From ff6a4e4ba947d6424997406dd122f24489f4081c Mon Sep 17 00:00:00 2001 From: Christian Marillat Date: Mon, 10 Aug 2026 20:36:30 +0200 Subject: [PATCH] _git-submodules Gbp-Pq: Name 01_git-submodules.patch --- deps/try_signal/.travis.yml | 44 + deps/try_signal/CMakeLists.txt | 6 + deps/try_signal/Jamfile | 18 + deps/try_signal/LICENSE | 29 + deps/try_signal/README.rst | 53 + deps/try_signal/appveyor.yml | 50 + deps/try_signal/example.cpp | 31 + deps/try_signal/signal_error_code.cpp | 209 + deps/try_signal/signal_error_code.hpp | 162 + deps/try_signal/test.cpp | 47 + deps/try_signal/try_signal.cpp | 144 + deps/try_signal/try_signal.hpp | 49 + deps/try_signal/try_signal_mingw.hpp | 78 + deps/try_signal/try_signal_msvc.hpp | 61 + deps/try_signal/try_signal_posix.hpp | 82 + simulation/libsimulator/CMakeLists.txt | 61 + simulation/libsimulator/Jamfile | 136 + simulation/libsimulator/LICENSE | 675 + simulation/libsimulator/README.rst | 199 + .../libsimulator/include/simulator/chrono.hpp | 87 + .../libsimulator/include/simulator/config.hpp | 51 + .../include/simulator/function.hpp | 220 + .../include/simulator/handler_allocator.hpp | 81 + .../include/simulator/http_proxy.hpp | 103 + .../include/simulator/http_server.hpp | 134 + .../include/simulator/mallocator.hpp | 69 + .../libsimulator/include/simulator/nat.hpp | 51 + .../include/simulator/noexcept_movable.hpp | 56 + .../libsimulator/include/simulator/packet.hpp | 94 + .../libsimulator/include/simulator/pcap.hpp | 42 + .../include/simulator/pop_warnings.hpp | 44 + .../include/simulator/push_warnings.hpp | 89 + .../libsimulator/include/simulator/queue.hpp | 100 + .../include/simulator/simulator.hpp | 1486 ++ .../libsimulator/include/simulator/sink.hpp | 47 + .../include/simulator/sink_forwarder.hpp | 43 + .../include/simulator/socks_server.hpp | 202 + .../libsimulator/include/simulator/ssl.hpp | 44 + .../libsimulator/include/simulator/utils.hpp | 47 + simulation/libsimulator/src/acceptor.cpp | 379 + .../libsimulator/src/default_config.cpp | 106 + .../src/high_resolution_clock.cpp | 51 + .../src/high_resolution_timer.cpp | 141 + simulation/libsimulator/src/http_proxy.cpp | 362 + simulation/libsimulator/src/http_server.cpp | 425 + simulation/libsimulator/src/io_service.cpp | 272 + simulation/libsimulator/src/nat.cpp | 47 + simulation/libsimulator/src/pcap.cpp | 204 + simulation/libsimulator/src/queue.cpp | 142 + simulation/libsimulator/src/resolver.cpp | 143 + simulation/libsimulator/src/simulation.cpp | 381 + simulation/libsimulator/src/simulator.cpp | 262 + .../libsimulator/src/sink_forwarder.cpp | 45 + simulation/libsimulator/src/socks_server.cpp | 1033 ++ simulation/libsimulator/src/tcp_socket.cpp | 929 + simulation/libsimulator/src/udp_socket.cpp | 492 + simulation/libsimulator/test/acceptor.cpp | 183 + simulation/libsimulator/test/catch.hpp | 14057 ++++++++++++++++ simulation/libsimulator/test/main.cpp | 11638 +++++++++++++ simulation/libsimulator/test/multi_accept.cpp | 129 + simulation/libsimulator/test/multi_homed.cpp | 212 + simulation/libsimulator/test/null_buffers.cpp | 200 + .../libsimulator/test/parse_request.cpp | 76 + simulation/libsimulator/test/resolver.cpp | 192 + simulation/libsimulator/test/timer.cpp | 91 + simulation/libsimulator/test/udp_socket.cpp | 114 + simulation/libsimulator/user-config.jam | 3 + 67 files changed, 37533 insertions(+) create mode 100644 deps/try_signal/.travis.yml create mode 100644 deps/try_signal/CMakeLists.txt create mode 100644 deps/try_signal/Jamfile create mode 100644 deps/try_signal/LICENSE create mode 100644 deps/try_signal/README.rst create mode 100644 deps/try_signal/appveyor.yml create mode 100644 deps/try_signal/example.cpp create mode 100644 deps/try_signal/signal_error_code.cpp create mode 100644 deps/try_signal/signal_error_code.hpp create mode 100644 deps/try_signal/test.cpp create mode 100644 deps/try_signal/try_signal.cpp create mode 100644 deps/try_signal/try_signal.hpp create mode 100644 deps/try_signal/try_signal_mingw.hpp create mode 100644 deps/try_signal/try_signal_msvc.hpp create mode 100644 deps/try_signal/try_signal_posix.hpp create mode 100644 simulation/libsimulator/CMakeLists.txt create mode 100644 simulation/libsimulator/Jamfile create mode 100644 simulation/libsimulator/LICENSE create mode 100644 simulation/libsimulator/README.rst create mode 100644 simulation/libsimulator/include/simulator/chrono.hpp create mode 100644 simulation/libsimulator/include/simulator/config.hpp create mode 100644 simulation/libsimulator/include/simulator/function.hpp create mode 100644 simulation/libsimulator/include/simulator/handler_allocator.hpp create mode 100644 simulation/libsimulator/include/simulator/http_proxy.hpp create mode 100644 simulation/libsimulator/include/simulator/http_server.hpp create mode 100644 simulation/libsimulator/include/simulator/mallocator.hpp create mode 100644 simulation/libsimulator/include/simulator/nat.hpp create mode 100644 simulation/libsimulator/include/simulator/noexcept_movable.hpp create mode 100644 simulation/libsimulator/include/simulator/packet.hpp create mode 100644 simulation/libsimulator/include/simulator/pcap.hpp create mode 100644 simulation/libsimulator/include/simulator/pop_warnings.hpp create mode 100644 simulation/libsimulator/include/simulator/push_warnings.hpp create mode 100644 simulation/libsimulator/include/simulator/queue.hpp create mode 100644 simulation/libsimulator/include/simulator/simulator.hpp create mode 100644 simulation/libsimulator/include/simulator/sink.hpp create mode 100644 simulation/libsimulator/include/simulator/sink_forwarder.hpp create mode 100644 simulation/libsimulator/include/simulator/socks_server.hpp create mode 100644 simulation/libsimulator/include/simulator/ssl.hpp create mode 100644 simulation/libsimulator/include/simulator/utils.hpp create mode 100644 simulation/libsimulator/src/acceptor.cpp create mode 100644 simulation/libsimulator/src/default_config.cpp create mode 100644 simulation/libsimulator/src/high_resolution_clock.cpp create mode 100644 simulation/libsimulator/src/high_resolution_timer.cpp create mode 100644 simulation/libsimulator/src/http_proxy.cpp create mode 100644 simulation/libsimulator/src/http_server.cpp create mode 100644 simulation/libsimulator/src/io_service.cpp create mode 100644 simulation/libsimulator/src/nat.cpp create mode 100644 simulation/libsimulator/src/pcap.cpp create mode 100644 simulation/libsimulator/src/queue.cpp create mode 100644 simulation/libsimulator/src/resolver.cpp create mode 100644 simulation/libsimulator/src/simulation.cpp create mode 100644 simulation/libsimulator/src/simulator.cpp create mode 100644 simulation/libsimulator/src/sink_forwarder.cpp create mode 100644 simulation/libsimulator/src/socks_server.cpp create mode 100644 simulation/libsimulator/src/tcp_socket.cpp create mode 100644 simulation/libsimulator/src/udp_socket.cpp create mode 100644 simulation/libsimulator/test/acceptor.cpp create mode 100644 simulation/libsimulator/test/catch.hpp create mode 100644 simulation/libsimulator/test/main.cpp create mode 100644 simulation/libsimulator/test/multi_accept.cpp create mode 100644 simulation/libsimulator/test/multi_homed.cpp create mode 100644 simulation/libsimulator/test/null_buffers.cpp create mode 100644 simulation/libsimulator/test/parse_request.cpp create mode 100644 simulation/libsimulator/test/resolver.cpp create mode 100644 simulation/libsimulator/test/timer.cpp create mode 100644 simulation/libsimulator/test/udp_socket.cpp create mode 100644 simulation/libsimulator/user-config.jam diff --git a/deps/try_signal/.travis.yml b/deps/try_signal/.travis.yml new file mode 100644 index 0000000..778d067 --- /dev/null +++ b/deps/try_signal/.travis.yml @@ -0,0 +1,44 @@ +language: cpp +matrix: + include: + - env: toolset=gcc + - os: osx + osx_image: xcode11.2 + env: toolset=darwin + +branches: + only: + - master + +git: + submodules: false + depth: 1 + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - libboost-tools-dev + - g++-9 + +install: + + - 'if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update > /dev/null && brew install boost-build; fi' + - 'if [[ $TRAVIS_OS_NAME != "osx" ]]; then + export B2=bjam; + else + export B2=b2; + fi' + - touch ~/user-config.jam + - 'if [[ $toolset == "gcc" ]]; then + g++-5 --version; + echo "using gcc : : g++-5 : -std=c++11 ;" >> ~/user-config.jam; + fi' + - 'echo "using darwin : : clang++ : -std=c++11 ;" >> ~/user-config.jam' + +script: + + - ${B2} link=static stage_test + - ./test + diff --git a/deps/try_signal/CMakeLists.txt b/deps/try_signal/CMakeLists.txt new file mode 100644 index 0000000..945cd6c --- /dev/null +++ b/deps/try_signal/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12) +project(try_signal) + +add_library(try_signal signal_error_code try_signal) +target_include_directories(try_signal PUBLIC .) + diff --git a/deps/try_signal/Jamfile b/deps/try_signal/Jamfile new file mode 100644 index 0000000..ec001a8 --- /dev/null +++ b/deps/try_signal/Jamfile @@ -0,0 +1,18 @@ +lib try_signal + : # sources + signal_error_code.cpp try_signal.cpp + : # requirements + : # default build + static + : # usage requirements + . + ; + +exe test : test.cpp : try_signal static ; +explicit test ; + +exe example : example.cpp : try_signal static ; +explicit example ; + +install stage_test : test : . ; + diff --git a/deps/try_signal/LICENSE b/deps/try_signal/LICENSE new file mode 100644 index 0000000..1523026 --- /dev/null +++ b/deps/try_signal/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/deps/try_signal/README.rst b/deps/try_signal/README.rst new file mode 100644 index 0000000..22cbe18 --- /dev/null +++ b/deps/try_signal/README.rst @@ -0,0 +1,53 @@ +try_signal +========== + +.. image:: https://travis-ci.org/arvidn/try_signal.svg?branch=master + :target: https://travis-ci.org/arvidn/try_signal + +.. image:: https://ci.appveyor.com/api/projects/status/le8jjroaai8081f1?svg=true + :target: https://ci.appveyor.com/project/arvidn/try-signal/branch/master + +The ``try_signal`` library provide a way to turn signals into C++ exceptions. +This is especially useful when performing disk I/O via memory mapped files, +where I/O errors are reported as ``SIGBUS`` and ``SIGSEGV`` or as structured +exceptions on windows. + +The function ``try_signal`` takes a function object that will be executed once. +If the function causes a signal (or structured exception) to be raised, it will +throw a C++ exception. Note that RAII may not be relied upon within this function. +It may not rely on destructors being called. Stick to simple operations like +memcopy. + +Example:: + + #include + #include + #include + #include "try_signal.hpp" + #include + #include + #include + + int main() try + { + int fd = open("test_file", O_RDWR); + void* map = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + std::vector buf(1024); + std::iota(buf.begin(), buf.end(), 0); + + // disk full or access after EOF are reported as exceptions + sig::try_signal([&]{ + std::memcpy(map, buf.data(), buf.size()); + }); + + munmap(map, 1024); + close(fd); + return 0; + } + catch (std::exception const& e) + { + fprintf(stderr, "exited with exception: %s\n", e.what()); + return 1; + } + diff --git a/deps/try_signal/appveyor.yml b/deps/try_signal/appveyor.yml new file mode 100644 index 0000000..a185535 --- /dev/null +++ b/deps/try_signal/appveyor.yml @@ -0,0 +1,50 @@ +version: "{build}" +branches: + only: + - master +os: Visual Studio 2015 +clone_depth: 1 +environment: + matrix: + - variant: debug + compiler: msvc-14.0 + model: 64 + - variant: debug + compiler: msvc-14.0 + model: 32 + - variant: release + compiler: msvc-14.0 + model: 64 + - variant: debug + compiler: gcc + model: 32 + - variant: debug + compiler: gcc + model: 64 + - variant: release + compiler: gcc + model: 32 + +install: +- set ROOT_DIRECTORY=%CD% +- set BOOST_ROOT=c:\Libraries\boost_1_67_0 +- set BOOST_BUILD_PATH=%BOOST_ROOT%\tools\build +- echo %BOOST_ROOT% +- echo %BOOST_BUILD_PATH% +- set PATH=%PATH%;%BOOST_BUILD_PATH%\src\engine\bin.ntx86 +- ps: '"using msvc : 14.0 ;`nusing gcc : : : -std=c++11 ;" | Set-Content $env:HOMEDRIVE\$env:HOMEPATH\user-config.jam' +- type %HOMEDRIVE%%HOMEPATH%\user-config.jam +- set PATH=c:\msys64\mingw32\bin;%PATH% +- g++ --version +- python --version +- echo %ROOT_DIRECTORY% +- cd %BOOST_BUILD_PATH%\src\engine +- build.bat >nul +- cd %ROOT_DIRECTORY% + +build_script: +# examples +- b2.exe warnings=all warnings-as-errors=on -j2 %compiler% address-model=%model% variant=%variant% stage_test + +test_script: +- test diff --git a/deps/try_signal/example.cpp b/deps/try_signal/example.cpp new file mode 100644 index 0000000..703b817 --- /dev/null +++ b/deps/try_signal/example.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include "try_signal.hpp" +#include +#include +#include + +int main() try +{ + int fd = open("test_file", O_RDWR); + void* map = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + std::vector buf(1024); + std::iota(buf.begin(), buf.end(), 0); + + // disk full or access after EOF are reported as exceptions + sig::try_signal([&]{ + std::memcpy(map, buf.data(), buf.size()); + }); + + munmap(map, 1024); + close(fd); + return 0; +} +catch (std::exception const& e) +{ + fprintf(stderr, "exited with exception: %s\n", e.what()); + return 1; +} + diff --git a/deps/try_signal/signal_error_code.cpp b/deps/try_signal/signal_error_code.cpp new file mode 100644 index 0000000..ae016cf --- /dev/null +++ b/deps/try_signal/signal_error_code.cpp @@ -0,0 +1,209 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include "signal_error_code.hpp" + +namespace { + + struct signal_error_category : std::error_category + { + const char* name() const noexcept override + { return "signal"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(abort) + SIGNAL_CASE(alarm) + SIGNAL_CASE(arithmetic_exception) + SIGNAL_CASE(hangup) + SIGNAL_CASE(illegal) + SIGNAL_CASE(interrupt) + SIGNAL_CASE(kill) + SIGNAL_CASE(pipe) + SIGNAL_CASE(quit) + case sig::errors::error_code_enum::segmentation: return "segmentation fault"; + SIGNAL_CASE(terminate) + SIGNAL_CASE(user1) + SIGNAL_CASE(user2) + SIGNAL_CASE(child) + SIGNAL_CASE(cont) + SIGNAL_CASE(stop) + SIGNAL_CASE(terminal_stop) + SIGNAL_CASE(terminal_in) + SIGNAL_CASE(terminal_out) + SIGNAL_CASE(bus) +#ifdef SIGPOLL + SIGNAL_CASE(poll) +#endif + SIGNAL_CASE(profiler) + SIGNAL_CASE(system_call) + SIGNAL_CASE(trap) + SIGNAL_CASE(urgent_data) + SIGNAL_CASE(virtual_timer) + SIGNAL_CASE(cpu_limit) + SIGNAL_CASE(file_size_limit) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return {ev, *this}; } + }; +} // anonymous namespace + +namespace sig { +namespace errors { + + std::error_code make_error_code(error_code_enum e) + { + return {e, sig_category()}; + } + + std::error_condition make_error_condition(error_code_enum e) + { + return {e, sig_category()}; + } + +} // namespace errors + +std::error_category& sig_category() +{ + static signal_error_category signal_category; + return signal_category; +} + +#ifdef _WIN32 + +namespace { + sig::errors::error_code_enum map_exception_code(int const ev) + { + switch (ev) + { + case seh_errors::error_code_enum::access_violation: + case seh_errors::error_code_enum::array_bounds_exceeded: + case seh_errors::error_code_enum::guard_page: + case seh_errors::error_code_enum::stack_overflow: + case seh_errors::error_code_enum::flt_stack_check: + case seh_errors::error_code_enum::in_page_error: + return sig::errors::segmentation; + case seh_errors::error_code_enum::breakpoint: + case seh_errors::error_code_enum::single_step: + return sig::errors::trap; + case seh_errors::error_code_enum::datatype_misalignment: + return sig::errors::bus; + case seh_errors::error_code_enum::flt_denormal_operand: + case seh_errors::error_code_enum::flt_divide_by_zero: + case seh_errors::error_code_enum::flt_inexact_result: + case seh_errors::error_code_enum::flt_invalid_operation: + case seh_errors::error_code_enum::flt_overflow: + case seh_errors::error_code_enum::flt_underflow: + case seh_errors::error_code_enum::int_divide_by_zero: + case seh_errors::error_code_enum::int_overflow: + return sig::errors::arithmetic_exception; + case seh_errors::error_code_enum::illegal_instruction: + case seh_errors::error_code_enum::invalid_disposition: + case seh_errors::error_code_enum::priv_instruction: + case seh_errors::error_code_enum::noncontinuable_exception: + case seh_errors::error_code_enum::status_unwind_consolidate: + return sig::errors::illegal; + case seh_errors::error_code_enum::invalid_handle: + return sig::errors::pipe; + default: + return sig::errors::illegal; + } + } + + struct seh_error_category : std::error_category + { + const char* name() const noexcept override + { return "SEH"; } + std::string message(int ev) const noexcept override + { +#define SIGNAL_CASE(x) case sig::seh_errors::error_code_enum:: x: return #x; + switch (ev) + { + SIGNAL_CASE(access_violation) + SIGNAL_CASE(array_bounds_exceeded) + SIGNAL_CASE(guard_page) + SIGNAL_CASE(stack_overflow) + SIGNAL_CASE(flt_stack_check) + SIGNAL_CASE(in_page_error) + SIGNAL_CASE(breakpoint) + SIGNAL_CASE(single_step) + SIGNAL_CASE(datatype_misalignment) + SIGNAL_CASE(flt_denormal_operand) + SIGNAL_CASE(flt_divide_by_zero) + SIGNAL_CASE(flt_inexact_result) + SIGNAL_CASE(flt_invalid_operation) + SIGNAL_CASE(flt_overflow) + SIGNAL_CASE(flt_underflow) + SIGNAL_CASE(int_divide_by_zero) + SIGNAL_CASE(int_overflow) + SIGNAL_CASE(illegal_instruction) + SIGNAL_CASE(invalid_disposition) + SIGNAL_CASE(priv_instruction) + SIGNAL_CASE(noncontinuable_exception) + SIGNAL_CASE(status_unwind_consolidate) + SIGNAL_CASE(invalid_handle) + default: return "unknown"; + } +#undef SIGNAL_CASE + } + std::error_condition default_error_condition(int ev) const noexcept override + { return std::error_condition(map_exception_code(ev), sig_category()); } + }; +} // anonymous namespace + +namespace seh_errors { + + std::error_code make_error_code(error_code_enum e) + { + return {static_cast(e), seh_category()}; + } + +} // namespace errors + +std::error_category& seh_category() +{ + static seh_error_category seh_category; + return seh_category; +} + +#endif + +} // namespace sig + diff --git a/deps/try_signal/signal_error_code.hpp b/deps/try_signal/signal_error_code.hpp new file mode 100644 index 0000000..91ea947 --- /dev/null +++ b/deps/try_signal/signal_error_code.hpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SIGNAL_ERROR_CODE_HPP_INCLUDED +#define SIGNAL_ERROR_CODE_HPP_INCLUDED + +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#ifdef __GNUC__ +#include +#else +#include +#endif +#endif + +namespace sig { +namespace errors { + +#ifdef _WIN32 +#define SIG_ENUM(name, sig) name, +#else +#define SIG_ENUM(name, sig) name = sig, +#endif + + enum error_code_enum: int + { + SIG_ENUM(abort, SIGABRT) + SIG_ENUM(alarm, SIGALRM) + SIG_ENUM(arithmetic_exception, SIGFPE) + SIG_ENUM(hangup, SIGHUP) + SIG_ENUM(illegal, SIGILL) + SIG_ENUM(interrupt, SIGINT) + SIG_ENUM(kill, SIGKILL) + SIG_ENUM(pipe, SIGPIPE) + SIG_ENUM(quit, SIGQUIT) + SIG_ENUM(segmentation, SIGSEGV) + SIG_ENUM(terminate, SIGTERM) + SIG_ENUM(user1, SIGUSR1) + SIG_ENUM(user2, SIGUSR2) + SIG_ENUM(child, SIGCHLD) + SIG_ENUM(cont, SIGCONT) + SIG_ENUM(stop, SIGSTOP) + SIG_ENUM(terminal_stop, SIGTSTP) + SIG_ENUM(terminal_in, SIGTTIN) + SIG_ENUM(terminal_out, SIGTTOU) + SIG_ENUM(bus, SIGBUS) +#ifdef SIGPOLL + SIG_ENUM(poll, SIGPOLL) +#endif + SIG_ENUM(profiler, SIGPROF) + SIG_ENUM(system_call, SIGSYS) + SIG_ENUM(trap, SIGTRAP) + SIG_ENUM(urgent_data, SIGURG) + SIG_ENUM(virtual_timer, SIGVTALRM) + SIG_ENUM(cpu_limit, SIGXCPU) + SIG_ENUM(file_size_limit, SIGXFSZ) + }; + +#undef SIG_ENUM + + std::error_code make_error_code(error_code_enum e); + std::error_condition make_error_condition(error_code_enum e); + +} // namespace errors + +std::error_category& sig_category(); + +#ifdef _WIN32 +namespace seh_errors { + + // standard error codes are "int", the win32 exceptions are DWORD (i.e. + // unsigned int). We coerce them into int here for compatibility, and we're + // not concerned about their arithmetic + enum error_code_enum: int + { + access_violation = int(EXCEPTION_ACCESS_VIOLATION), + array_bounds_exceeded = int(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), + guard_page = int(EXCEPTION_GUARD_PAGE), + stack_overflow = int(EXCEPTION_STACK_OVERFLOW), + flt_stack_check = int(EXCEPTION_FLT_STACK_CHECK), + in_page_error = int(EXCEPTION_IN_PAGE_ERROR), + breakpoint = int(EXCEPTION_BREAKPOINT), + single_step = int(EXCEPTION_SINGLE_STEP), + datatype_misalignment = int(EXCEPTION_DATATYPE_MISALIGNMENT), + flt_denormal_operand = int(EXCEPTION_FLT_DENORMAL_OPERAND), + flt_divide_by_zero = int(EXCEPTION_FLT_DIVIDE_BY_ZERO), + flt_inexact_result = int(EXCEPTION_FLT_INEXACT_RESULT), + flt_invalid_operation = int(EXCEPTION_FLT_INVALID_OPERATION), + flt_overflow = int(EXCEPTION_FLT_OVERFLOW), + flt_underflow = int(EXCEPTION_FLT_UNDERFLOW), + int_divide_by_zero = int(EXCEPTION_INT_DIVIDE_BY_ZERO), + int_overflow = int(EXCEPTION_INT_OVERFLOW), + illegal_instruction = int(EXCEPTION_ILLEGAL_INSTRUCTION), + invalid_disposition = int(EXCEPTION_INVALID_DISPOSITION), + priv_instruction = int(EXCEPTION_PRIV_INSTRUCTION), + noncontinuable_exception = int(EXCEPTION_NONCONTINUABLE_EXCEPTION), + status_unwind_consolidate = int(STATUS_UNWIND_CONSOLIDATE), + invalid_handle = int(EXCEPTION_INVALID_HANDLE), + }; + + std::error_code make_error_code(error_code_enum e); +} + +std::error_category& seh_category(); + +#endif // _WIN32 + +} // namespace sig + +namespace std +{ +template<> +struct is_error_code_enum : std::true_type {}; + +template<> +struct is_error_condition_enum : std::true_type {}; + +#ifdef _WIN32 +template<> +struct is_error_code_enum : std::true_type {}; +#endif + +} // namespace std + +#endif + diff --git a/deps/try_signal/test.cpp b/deps/try_signal/test.cpp new file mode 100644 index 0000000..b8c67c8 --- /dev/null +++ b/deps/try_signal/test.cpp @@ -0,0 +1,47 @@ +#include +#include +#include // for memcpy + +#include "try_signal.hpp" + +int main() +{ + char const buf[] = "test...test"; + char dest[sizeof(buf)]; + + { + sig::try_signal([&]{ + std::memcpy(dest, buf, sizeof(buf)); + }); + if (!std::equal(buf, buf + sizeof(buf), dest)) { + fprintf(stderr, "ERROR: buffer not copied correctly\n"); + return 1; + } + } + + try { + void* invalid_pointer = nullptr; + sig::try_signal([&]{ + std::memcpy(dest, buf, sizeof(buf)); + std::memcpy(dest, invalid_pointer, sizeof(buf)); + }); + } + catch (std::system_error const& e) + { + if (e.code() != std::error_condition(sig::errors::segmentation)) { + fprintf(stderr, "ERROR: expected segmentaiton violation error\n"); + } + else { + fprintf(stderr, "OK\n"); + } + fprintf(stderr, "exited with expected system_error exception: %s\n", e.what()); + + // we expect this to happen, so return 0 + return e.code() == std::error_condition(sig::errors::segmentation) ? 0 : 1; + } + + // return non-zero here because we don't expect this + fprintf(stderr, "ERROR: expected exit through exception\n"); + return 1; +} + diff --git a/deps/try_signal/try_signal.cpp b/deps/try_signal/try_signal.cpp new file mode 100644 index 0000000..be5cd8d --- /dev/null +++ b/deps/try_signal/try_signal.cpp @@ -0,0 +1,144 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include + +#include "try_signal.hpp" + +#if !defined _WIN32 +// linux + +namespace sig { +namespace detail { + +namespace { +thread_local sigjmp_buf* jmpbuf = nullptr; +} + +std::atomic_flag once = ATOMIC_FLAG_INIT; + +scoped_jmpbuf::scoped_jmpbuf(sigjmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); +} + +scoped_jmpbuf::~scoped_jmpbuf() { jmpbuf = _previous_ptr; } + +void handler(int const signo, siginfo_t*, void*) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + siglongjmp(*jmpbuf, signo); + + // this signal was not caused within the scope of a try_signal object, + // invoke the default handler + signal(signo, SIG_DFL); + raise(signo); +} + +void setup_handler() +{ + struct sigaction sa; + sa.sa_sigaction = &sig::detail::handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &sa, nullptr); + sigaction(SIGBUS, &sa, nullptr); +} + +} // detail namespace +} // sig namespace + +#elif __GNUC__ +// mingw + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +thread_local jmp_buf* jmpbuf = nullptr; + +long CALLBACK handler(EXCEPTION_POINTERS* pointers) +{ + std::atomic_signal_fence(std::memory_order_acquire); + if (jmpbuf) + longjmp(*jmpbuf, pointers->ExceptionRecord->ExceptionCode); + return EXCEPTION_CONTINUE_SEARCH; +} + +scoped_handler::scoped_handler(jmp_buf* ptr) +{ + _previous_ptr = jmpbuf; + jmpbuf = ptr; + std::atomic_signal_fence(std::memory_order_release); + _handle = AddVectoredExceptionHandler(1, sig::detail::handler); +} +scoped_handler::~scoped_handler() +{ + RemoveVectoredExceptionHandler(_handle); + jmpbuf = _previous_ptr; +} + +} // detail namespace +} // sig namespace + +#else +// windows + +#include // for EXCEPTION_* + +namespace sig { +namespace detail { + + // these are the kinds of SEH exceptions we'll translate into C++ exceptions + bool catch_error(int const code) + { + return code == EXCEPTION_IN_PAGE_ERROR + || code == EXCEPTION_ACCESS_VIOLATION + || code == EXCEPTION_ARRAY_BOUNDS_EXCEEDED; + } +} // detail namespace +} // namespace sig + +#endif // _WIN32 + + diff --git a/deps/try_signal/try_signal.hpp b/deps/try_signal/try_signal.hpp new file mode 100644 index 0000000..557d92b --- /dev/null +++ b/deps/try_signal/try_signal.hpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_HPP_INCLUDED +#define TRY_SIGNAL_HPP_INCLUDED + +#if !defined _WIN32 +// linux +#include "try_signal_posix.hpp" +#elif __GNUC__ +// mingw +#include "try_signal_mingw.hpp" +#else +// windows +#include "try_signal_msvc.hpp" +#endif + + +#endif // TRY_SIGNAL_HPP_INCLUDED + diff --git a/deps/try_signal/try_signal_mingw.hpp b/deps/try_signal/try_signal_mingw.hpp new file mode 100644 index 0000000..e4db043 --- /dev/null +++ b/deps/try_signal/try_signal_mingw.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_MINGW_HPP_INCLUDED +#define TRY_SIGNAL_MINGW_HPP_INCLUDED + +#include "signal_error_code.hpp" + +#include // for jmp_buf + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +namespace sig { +namespace detail { + +struct scoped_handler +{ + scoped_handler(jmp_buf* ptr); + ~scoped_handler(); + scoped_handler(scoped_handler const&) = delete; + scoped_handler& operator=(scoped_handler const&) = delete; +private: + void* _handle; + jmp_buf* _previous_ptr; +}; + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + jmp_buf buf; + int const code = setjmp(buf); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_handler scope(&buf); + if (code != 0) + throw std::system_error(std::error_code(code, seh_category())); + + f(); +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_msvc.hpp b/deps/try_signal/try_signal_msvc.hpp new file mode 100644 index 0000000..04cc62d --- /dev/null +++ b/deps/try_signal/try_signal_msvc.hpp @@ -0,0 +1,61 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_MSVC_HPP_INCLUDED +#define TRY_SIGNAL_MSVC_HPP_INCLUDED + +#include "signal_error_code.hpp" + +namespace sig { +namespace detail { + +bool catch_error(int const code); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + __try + { + f(); + } + __except (detail::catch_error(GetExceptionCode())) + { + throw std::system_error(std::error_code(GetExceptionCode(), seh_category())); + } +} + +} // sig namespace + +#endif + diff --git a/deps/try_signal/try_signal_posix.hpp b/deps/try_signal/try_signal_posix.hpp new file mode 100644 index 0000000..2c4615d --- /dev/null +++ b/deps/try_signal/try_signal_posix.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TRY_SIGNAL_POSIX_HPP_INCLUDED +#define TRY_SIGNAL_POSIX_HPP_INCLUDED + +#include "signal_error_code.hpp" +#include // for sigjmp_buf +#include + +namespace sig { + +namespace detail { + +extern std::atomic_flag once; + +struct scoped_jmpbuf +{ + explicit scoped_jmpbuf(sigjmp_buf* ptr); + ~scoped_jmpbuf(); + scoped_jmpbuf(scoped_jmpbuf const&) = delete; + scoped_jmpbuf& operator=(scoped_jmpbuf const&) = delete; +private: + sigjmp_buf* _previous_ptr; +}; + +void handler(int const signo, siginfo_t* si, void*); +void setup_handler(); + +} // detail namespace + +template +void try_signal(Fun&& f) +{ + if (sig::detail::once.test_and_set() == false) { + sig::detail::setup_handler(); + } + + sigjmp_buf buf; + int const sig = sigsetjmp(buf, 1); + // set the thread local jmpbuf pointer, and make sure it's cleared when we + // leave the scope + sig::detail::scoped_jmpbuf scope(&buf); + if (sig != 0) + throw std::system_error(static_cast(sig)); + + f(); +} + +} + +#endif + diff --git a/simulation/libsimulator/CMakeLists.txt b/simulation/libsimulator/CMakeLists.txt new file mode 100644 index 0000000..a805d25 --- /dev/null +++ b/simulation/libsimulator/CMakeLists.txt @@ -0,0 +1,61 @@ +project(libsimulator) +cmake_minimum_required(VERSION 2.8.7) + +set(SRC_DIR src) +set(TEST_SRC_DIR test) +set(INCLUDE_DIR include) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Boost.System is header-only (since Boost 1.69), so we only need the Boost +# headers. Requesting the "system" component fails with recent Boost releases +# that no longer ship a boost_system CMake config file. +find_package(Boost REQUIRED) +find_package(Threads REQUIRED) + +set(SIMULATOR_SRC_FILES + ${SRC_DIR}/acceptor.cpp + ${SRC_DIR}/high_resolution_timer.cpp + ${SRC_DIR}/io_service.cpp + ${SRC_DIR}/resolver.cpp + ${SRC_DIR}/sink_forwarder.cpp + ${SRC_DIR}/udp_socket.cpp + ${SRC_DIR}/default_config.cpp + ${SRC_DIR}/http_proxy.cpp + ${SRC_DIR}/pcap.cpp + ${SRC_DIR}/simulation.cpp + ${SRC_DIR}/socks_server.cpp + ${SRC_DIR}/high_resolution_clock.cpp + ${SRC_DIR}/http_server.cpp + ${SRC_DIR}/queue.cpp + ${SRC_DIR}/simulator.cpp + ${SRC_DIR}/tcp_socket.cpp + ${SRC_DIR}/nat.cpp +) + +add_library(simulator ${SIMULATOR_SRC_FILES}) +target_include_directories(simulator PUBLIC ${INCLUDE_DIR}) +target_link_libraries(simulator PUBLIC Boost::boost) + +if(WIN32) + target_link_libraries(simulator PRIVATE ws2_32) +endif() + +enable_testing() + +function(define_test) + set(NAME ${ARGV0}) + add_executable(${NAME} ${TEST_SRC_DIR}/${NAME}.cpp ${TEST_SRC_DIR}/main.cpp) + target_link_libraries(${NAME} simulator) + target_link_libraries(${NAME} Threads::Threads) + add_test(NAME ${NAME} COMMAND ${NAME}) +endfunction() + +define_test(acceptor) +define_test(multi_accept) +define_test(multi_homed) +define_test(null_buffers) +define_test(parse_request) +define_test(resolver) +define_test(timer) +define_test(udp_socket) diff --git a/simulation/libsimulator/Jamfile b/simulation/libsimulator/Jamfile new file mode 100644 index 0000000..b9cf9b9 --- /dev/null +++ b/simulation/libsimulator/Jamfile @@ -0,0 +1,136 @@ +# This Jamfile requires boost-build v2 to build. + +import path ; +import modules ; +import os ; +import testing ; + +BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; + +ECHO "BOOST_ROOT =" $(BOOST_ROOT) ; +ECHO "OS =" [ os.name ] ; + +lib wsock32 : : wsock32 shared ; +lib ws2_32 : : ws2_32 shared ; + +if $(BOOST_ROOT) +{ + use-project /boost : $(BOOST_ROOT) ; + alias boost_system : /boost/system//boost_system ; +} +else +{ + local boost-lib-search-path = + /usr/local/opt/boost/lib + /opt/homebrew/lib + ; + + local boost-include-path = + /usr/local/opt/boost/include + /opt/homebrew/include + ; + + # boost_system is a header-only library now, but we still need to find the boost headers + alias boost_system : : : : $(boost-include-path) ; +} + +SOURCES = + simulator + simulation + io_service + high_resolution_timer + high_resolution_clock + tcp_socket + udp_socket + queue + acceptor + default_config + http_server + socks_server + resolver + http_proxy + sink_forwarder + pcap + nat + ; + +lib simulator + : # sources + src/$(SOURCES).cpp + + : # requirements + include + boost_system + windows:ws2_32 + windows:wsock32 + multi + + shared:SIMULATOR_BUILDING_SHARED + _CRT_SECURE_NO_WARNINGS + + # https://github.com/chriskohlhoff/asio/issues/290#issuecomment-377727614 + _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING + + BOOST_ASIO_DISABLE_BOOST_DATE_TIME + BOOST_ASIO_HAS_MOVE + BOOST_ASIO_ENABLE_CANCELIO + + # make sure asio uses std::chrono + BOOST_ASIO_HAS_STD_CHRONO + + # io_executor only implements the legacy (Networking TS v1) executor + # concept: this falls any_io_executor back to boost::asio::executor, + # which it can wrap, so boost::asio::ssl::stream's bookkeeping timers + # work for consumers that include simulator/ssl.hpp. + BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT + + # disable auto-link + BOOST_ALL_NO_LIB + + : # default build + all + on + # boost.asio has a global tss_ptr which is the head of a + # linked list of all invocations of run on that thread. This + # determines whether dispatch() will run the handler immediately + # not. The simulator relies on this. On windows each DLL will have + # its own copy of this variable, effectively causing a deadlock + # in the simulator. So, default to static linking + static + + : # usage requirements + BOOST_ASIO_DISABLE_BOOST_DATE_TIME + BOOST_ASIO_HAS_MOVE + BOOST_ASIO_ENABLE_CANCELIO + BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT + multi + include + shared:SIMULATOR_LINKING_SHARED + + # https://github.com/chriskohlhoff/asio/issues/290#issuecomment-377727614 + _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING + ; + +project + : requirements + simulator + # disable auto-link + BOOST_ALL_NO_LIB + : default-build + static + multi + 14 + ; + +test-suite simulator-tests : [ run + test/main.cpp + test/resolver.cpp + test/multi_homed.cpp + test/timer.cpp + test/acceptor.cpp + test/multi_accept.cpp + test/null_buffers.cpp + test/udp_socket.cpp + test/parse_request.cpp + ] ; + diff --git a/simulation/libsimulator/LICENSE b/simulation/libsimulator/LICENSE new file mode 100644 index 0000000..733c072 --- /dev/null +++ b/simulation/libsimulator/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/simulation/libsimulator/README.rst b/simulation/libsimulator/README.rst new file mode 100644 index 0000000..8fb68fa --- /dev/null +++ b/simulation/libsimulator/README.rst @@ -0,0 +1,199 @@ +libsimulator +============ + +.. image:: https://github.com/arvidn/libsimulator/actions/workflows/ci.yml/badge.svg?branch=master + :target: https://github.com/arvidn/libsimulator/actions/workflows/ci.yml + +*This is still in initial development, some of this README represents ambitions +rather than the current state* + +libsimulator is a library for running discrete event simulations, implementing +the ``boost.asio`` API (or a somewhat faithful emulation of a subset of it, +patches are welcome). This makes it practical to be used as a testing tool of +real implementations of network software as well as for writing simulators that +later turn into live production applications. + +The simulation has to have a single time-line to be deterministic, meaning it +must be single threaded and use a single ``io_service`` as the message queue. +These requirements may affect how the program to be tested is written. It may +for instance require that an external io_service can be provided rather than one +being wrapped in an internal thread. + +However, ``boost.asio`` programs may generally benefit from being transformed to +this form, as the become *composable*, i.e. agnostic to which io_service they +run on or how many threads are running it. + +features +-------- + +The currently (partially) supported classes are: + +* chrono::high_resolution_clock +* asio::high_resolution_timer +* asio::ip::tcp::acceptor +* asio::ip::tcp::endpoint +* asio::ip::address (v4 and v6 variants, these just defer to the actual + boost.asio types) +* asio::ip::tcp::socket +* asio::ip::udp::socket +* asio::io_service +* asio::ip::udp::resolver +* asio::ip::tcp::resolver + +The ``high_resolution_clock`` in the ``chrono`` namespace implements the timer +concept from the chrono library. + +usage +----- + +The ``io_service`` object is significantly different from the one in boost.asio. +This is because one simulation may only have a single message loop and a single +ordering of events. This single message loop is provided by the ``simulation`` +class. Each simulation should have only one such object. An ``io_service`` +object represents a single node on the network. When creating an io_service, you +have to pass in the simulation it belongs to as well as the IP address it should +have. It is also possible to pass in multiple addresses to form a multi-homed +node. For instance, one with both an IPv4 and IPv6 interface. + +When creating sockets, binding and connecting them, the io_service object +determines what ``INADDR_ANY`` resolves to (the first IP assigned to that node). + +The only aspects of the io_service interface that's preserved are ``post()``, +``dispatch()`` and constructing timers and sockets. In short, the ``run()`` and +``poll()`` family of functions do not exist. Every io_service object is assumed +to be run, and all of their events are handled by the simulation object. + +None of the synchronous APIs are supported, because that would require +integration with OS threads and scheduler. + +example +------- + +Here's a simple example illustrating the asio timer:: + + #include "simulator/simulator.hpp" + #include + #include + + void print_time(sim::asio::high_resolution_timer& timer + , boost::system::error_code const& ec) + { + using namespace sim::chrono; + static int counter = 0; + + printf("[%d] timer fired at: %d milliseconds. error: %s\n" + , counter + , int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()) + , ec.message().c_str()); + + ++counter; + if (counter < 5) + { + timer.expires_from_now(seconds(counter)); + timer.async_wait(std::bind(&print_time, std::ref(timer), _1)); + } + } + + int main() + { + using namespace sim::chrono; + + default_config cfg; + simulation sim(cfg); + io_service ios(sim, ip::address_v4::from_string("1.2.3.4")); + sim::asio::high_resolution_timer timer(ios); + + timer.expires_from_now(seconds(1)); + timer.async_wait(std::bind(&print_time, std::ref(timer), _1)); + + boost::system::error_code ec; + sim.run(ec); + + printf("sim::run() returned: %s at: %d\n" + , ec.message().c_str() + , int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count())); + } + +The output from this program is:: + + [0] timer fired at: 1000 milliseconds. error: Undefined error: 0 + [1] timer fired at: 2000 milliseconds. error: Undefined error: 0 + [2] timer fired at: 4000 milliseconds. error: Undefined error: 0 + [3] timer fired at: 7000 milliseconds. error: Undefined error: 0 + [4] timer fired at: 11000 milliseconds. error: Undefined error: 0 + io_service::run() returned: Undefined error: 0 at: 11000 + +And obviously it doesn't take 11 wall-clock seconds to run (it returns +instantly). + +configuration +------------- + +The simulated network can be configured with per-node pair bandwidth, round-trip +latency and queue sizes. This is controlled via a callback interface that +libsimulator will ask for these properties when nodes get connected. + +The resolution of hostnames is also configurable by providing a callback on the +configuration object along with the latency of individual lookups. + +To configure the network for the simulation, pass in a reference to an object +implementing the ``sim::configuration`` interface:: + + struct configuration + { + // build the network + virtual void build(simulation& sim) = 0; + + // return the hops on the network packets from src to dst need to traverse + virtual route channel_route(asio::ip::address src + , asio::ip::address dst) = 0; + + // return the hops an incoming packet to ep need to traverse before + // reaching the socket (for instance a NAT) + virtual route incoming_route(asio::ip::address ip) = 0; + + // return the hops an outgoing packet from ep need to traverse before + // reaching the network (for instance a DSL modem) + virtual route outgoing_route(asio::ip::address ip) = 0; + + // return the path MTU between the two IP addresses + // For TCP sockets, this will be called once when the connection is + // established. For UDP sockets it's called for every burst of packets + // that are sent + virtual int path_mtu(asio::ip::address ip1, asio::ip::address ip2) = 0; + + // called for every hostname lookup made by the client. ``reqyestor`` is + // the node performing the lookup, ``hostname`` is the name being looked + // up. Resolve the name into addresses and fill in ``result`` or set + // ``ec`` if the hostname is not found or some other error occurs. The + // return value is the latency of the lookup. The client's callback won't + // be called until after waiting this long. + virtual chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) = 0; + }; + +``build()`` is called right after the simulation is constructed. It gives the +configuration object an opportunity to construct the core queues, since they +need access to the simulator. + +``channel_route()`` is expected to return a *route* of network hops from the +source IP to the destination IP. A route is a series of ``sink`` objects. The +typical sink is a ``sim::queue``, which is a network node with a specific rate +limit, propagation delay and queue size. + +*TODO: finish document configuration interface* + +history +------- + +libsimulator grew out of libtorrent's unit tests, as a tool to make them reliable +and deterministic (i.e. not depend on external systems like sockets and timers) +and also easier to debug. The subset of the asio API initially supported by this +library is the subset used by libtorrent. Patches are welcome to improve +fidelity and support. + diff --git a/simulation/libsimulator/include/simulator/chrono.hpp b/simulation/libsimulator/include/simulator/chrono.hpp new file mode 100644 index 0000000..c84c338 --- /dev/null +++ b/simulation/libsimulator/include/simulator/chrono.hpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef CHRONO_HPP_INCLUDED +#define CHRONO_HPP_INCLUDED + +#include +#include "simulator/config.hpp" + +#if defined BOOST_ASIO_HAS_STD_CHRONO +#include +#else +#include "simulator/push_warnings.hpp" + +#include +#include +#include + +#include "simulator/pop_warnings.hpp" +#endif + +namespace sim { namespace chrono +{ +#if defined BOOST_ASIO_HAS_STD_CHRONO + using std::chrono::seconds; + using std::chrono::milliseconds; + using std::chrono::microseconds; + using std::chrono::nanoseconds; + using std::chrono::minutes; + using std::chrono::hours; + using std::chrono::duration_cast; + using std::chrono::time_point; + using std::chrono::duration; +#else + using boost::chrono::seconds; + using boost::chrono::milliseconds; + using boost::chrono::microseconds; + using boost::chrono::nanoseconds; + using boost::chrono::minutes; + using boost::chrono::hours; + using boost::chrono::duration_cast; + using boost::chrono::time_point; + using boost::chrono::duration; +#endif + + // std.chrono / boost.chrono compatible high_resolution_clock using a simulated time + struct SIMULATOR_DECL high_resolution_clock + { + using rep = std::int64_t; +#if defined BOOST_ASIO_HAS_STD_CHRONO + using period = std::nano; + using time_point = std::chrono::time_point; + using duration = std::chrono::duration; +#else + using period = boost::nano; + using time_point = time_point; + using duration = duration; +#endif + static const bool is_steady = true; + static time_point now(); + + // private interface + static void fast_forward(high_resolution_clock::duration d); + }; + + SIMULATOR_DECL void reset_clock(); + +} // chrono +} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/config.hpp b/simulation/libsimulator/include/simulator/config.hpp new file mode 100644 index 0000000..053a665 --- /dev/null +++ b/simulation/libsimulator/include/simulator/config.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef CONFIG_HPP_INCLUDED +#define CONFIG_HPP_INCLUDED + +#include "simulator/push_warnings.hpp" +#include +#include "simulator/pop_warnings.hpp" + +#ifdef SIMULATOR_BUILDING_SHARED +#define SIMULATOR_DECL BOOST_SYMBOL_EXPORT +#elif defined SIMULATOR_LINKING_SHARED +#define SIMULATOR_DECL BOOST_SYMBOL_IMPORT +#else +#define SIMULATOR_DECL +#endif + +#if defined __clang__ || defined __GNUC__ +#define LIBSIMULATOR_NO_RETURN __attribute((noreturn)) +#elif _MSC_VER +#define LIBSIMULATOR_NO_RETURN __declspec(noreturn) +#else +#define LIBSIMULATOR_NO_RETURN +#endif + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +// warning C4661: X: no suitable definition provided for explicit template instantiation request +#pragma warning( disable : 4661) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/function.hpp b/simulation/libsimulator/include/simulator/function.hpp new file mode 100644 index 0000000..5a67a2b --- /dev/null +++ b/simulation/libsimulator/include/simulator/function.hpp @@ -0,0 +1,220 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SIMULATOR_FUNCTION_HPP_INCLUDED +#define SIMULATOR_FUNCTION_HPP_INCLUDED + +#include +#include // for allocator_traits + +#include "simulator/mallocator.hpp" + +namespace sim { +namespace aux { + + template + T exchange_(T& var, U&& new_val) + { + T temp = std::move(var); + var = std::forward(new_val); + return temp; + } + + template + T* allocate_handler(Fun h) + { + using alloc = typename boost::asio::associated_allocator::type>::type; + using our_alloc = typename std::allocator_traits::template rebind_alloc; + our_alloc al(boost::asio::get_associated_allocator(h)); + void* ptr = al.allocate(1); + if (ptr == nullptr) throw std::bad_alloc(); + try { + return new (ptr) T(std::move(h)); + } + catch (...) { + al.deallocate(reinterpret_cast(ptr), 1); + throw; + } + } + + // this is a std::function-like class that supports move-only function + // objects + template + struct callable + { + using call_fun_t = R (*)(void*, A&&...); + using deallocate_fun_t = void (*)(void*); + call_fun_t call_fun; + deallocate_fun_t deallocate_fun; + }; + + template + R call_impl(void* mem, A&&... a); + + template + void dealloc_impl(void* mem); + + template + struct function_impl : callable + { + function_impl(Handler h) + : handler(std::move(h)) + { + this->call_fun = call_impl; + this->deallocate_fun = dealloc_impl; + } + Handler handler; + }; + + template + R call_impl(void* mem, A&&... a) + { + auto* obj = static_cast*>(mem); + Handler handler = std::move(obj->handler); + + obj->~function_impl(); + using alloc = typename boost::asio::associated_allocator::type; + using our_alloc = typename std::allocator_traits:: + template rebind_alloc::type>; + our_alloc al(boost::asio::get_associated_allocator(handler)); + al.deallocate(obj, 1); + + return handler(std::forward(a)...); + } + + template + void dealloc_impl(void* mem) + { + auto* obj = static_cast*>(mem); + Handler h = std::move(obj->handler); + + obj->~function_impl(); + using alloc = typename boost::asio::associated_allocator::type; + using our_alloc = typename std::allocator_traits:: + template rebind_alloc::type>; + our_alloc al(boost::asio::get_associated_allocator(h)); + al.deallocate(obj, 1); + } + + template + struct function; + + template + struct function + { + using result_type = R; + + using allocator_type = aux::mallocator; + allocator_type get_allocator() const { return allocator_type{}; } + + template + function(C c) + : m_callable(allocate_handler>(std::move(c))) + {} + function(function&& other) noexcept + : m_callable(exchange_(other.m_callable, nullptr)) + {} + function& operator=(function&& other) noexcept + { + if (&other == this) return *this; + clear(); + m_callable = exchange_(other.m_callable, nullptr); + return *this; + } + + ~function() { clear(); } + + // boost.asio requires handlers to be copy-constructible, but it will move + // them, if they're movable. So we trick asio into accepting this handler. + // If it attempts to copy, it will cause a link error + function(function const&) { assert(false && "functions should not be copied"); } + function& operator=(function const&) = delete; + + function() = default; + explicit operator bool() const { return m_callable != nullptr; } + function& operator=(std::nullptr_t) { clear(); return *this; } + void clear() + { + if (m_callable == nullptr) return; + auto fun = m_callable->deallocate_fun; + fun(m_callable); + m_callable = nullptr; + } + template + R operator()(Args&&... a) + { + assert(m_callable); + auto fun = m_callable->call_fun; + return fun(exchange_(m_callable, nullptr), std::forward(a)...); + } + private: + callable* m_callable = nullptr; + }; + + // index sequence, to unpack tuple + template struct seq {}; + template struct gens : gens {}; + template struct gens<0, S...> { using type = seq; }; + + // a binder for move-only types, and movable arguments. It's not a general + // binder as it doesn't support partial application, it just binds all + // arguments and ignores any arguments passed to the call + template + struct move_binder + { + move_binder(Callable c, A&&... a) + : m_args(std::move(a)...) + , m_callable(std::move(c)) + {} + + move_binder(move_binder const&) = delete; + move_binder& operator=(move_binder const&) = delete; + + move_binder(move_binder&&) = default; + move_binder& operator=(move_binder&&) = default; + + // ignore any arguments passed in. This is used to ignore an error_code + // argument for instance + template + R operator()(Args...) + { + return call(typename gens::type()); + } + + private: + + template + R call(seq) + { + return m_callable(std::move(std::get(m_args))...); + } + std::tuple m_args; + Callable m_callable; + }; + + template + move_binder move_bind(C c, A&&... a) + { + return move_binder(std::move(c), std::forward(a)...); + } + +} +} + +#endif + diff --git a/simulation/libsimulator/include/simulator/handler_allocator.hpp b/simulation/libsimulator/include/simulator/handler_allocator.hpp new file mode 100644 index 0000000..a653483 --- /dev/null +++ b/simulation/libsimulator/include/simulator/handler_allocator.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef HANDLER_ALLOCATOR_HPP_INCLUDED +#define HANDLER_ALLOCATOR_HPP_INCLUDED + +namespace sim +{ +namespace aux +{ + +template +struct malloc_allocator +{ + using value_type = T; + using size_type = std::size_t; + + friend bool operator==(malloc_allocator, malloc_allocator) { return true; } + friend bool operator!=(malloc_allocator, malloc_allocator) { return false; } + + template + struct rebind { using other = malloc_allocator; }; + + malloc_allocator() = default; + template + malloc_allocator(malloc_allocator const&) {} + + T* allocate(std::size_t size) { return static_cast(std::malloc(size * sizeof(T))); } + void deallocate(T* pointer, std::size_t) { std::free(pointer); } + using is_always_equal = std::true_type; +}; + +// this is a handler wrapper that customizes the asio handler allocator to use +// malloc instead of new. The purpose is to distinguish allocations that are +// internal to the simulator and allocations part of the program under test. +template +struct malloc_wrapper +{ + malloc_wrapper(Handler h) : m_handler(std::move(h)) {} + + template + void operator()(Args&&... a) + { + m_handler(std::forward(a)...); + } + + using allocator_type = malloc_allocator>; + + allocator_type get_allocator() const noexcept + { return allocator_type{}; } + +private: + Handler m_handler; +}; + +template +malloc_wrapper make_malloc(T h) +{ + return malloc_wrapper(std::move(h)); +} + +} +} + +#endif + diff --git a/simulation/libsimulator/include/simulator/http_proxy.hpp b/simulation/libsimulator/include/simulator/http_proxy.hpp new file mode 100644 index 0000000..bd1d6f7 --- /dev/null +++ b/simulation/libsimulator/include/simulator/http_proxy.hpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef HTTP_PROXY_HPP_INCLUDED +#define HTTP_PROXY_HPP_INCLUDED + +#include "simulator/simulator.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim +{ + struct http_request; + +// This is a very simple http proxy that only supports a single +// concurrent connection +struct SIMULATOR_DECL http_proxy +{ + http_proxy(asio::io_context& ios, unsigned short listen_port); + + void stop(); + +private: + + void on_accept(boost::system::error_code const& ec); + void on_read_request(boost::system::error_code const& ec, size_t bytes_transferred); + + void forward_request(http_request const& req); + void open_forward_connection(const asio::ip::tcp::endpoint& target); + void on_connected(boost::system::error_code const& ec); + + void on_domain_lookup(boost::system::error_code const& ec + , const asio::ip::tcp::resolver::results_type ips); + + void write_server_send_buffer(); + void on_server_write(boost::system::error_code const& ec, size_t bytes_transferred); + + void on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred); + void on_server_forward(boost::system::error_code const& ec, size_t bytes_transferred); + + void error(int code, char const* message); + void close_connection(); + + asio::ip::tcp::resolver m_resolver; + asio::ip::tcp::acceptor m_listen_socket; + + // this is the client connection, i.e. the client connecting to us, sending + // HTTP requests that we forward + asio::ip::tcp::socket m_client_connection; + // client endpoint + asio::ip::tcp::endpoint m_ep; + + // this is the connection to the server the client's requests are forwarded + // to + asio::ip::tcp::socket m_server_connection; + // true while there is an outstanding write operation to the server + bool m_writing_to_server; + + // receive buffer for requests from the client. i.e. client -> proxy (us) -> server + char m_client_in_buffer[65536]; + // buffer size + int m_num_client_in_bytes; + + char m_server_out_buffer[65536]; + int m_num_server_out_bytes; + + // receive buffer for incoming responses, i.e. server -> proxy (us) -> client + char m_in_buffer[65536]; + // buffer size + int m_num_in_bytes; + + // set to true when shutting down + bool m_close; +}; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/http_server.hpp b/simulation/libsimulator/include/simulator/http_server.hpp new file mode 100644 index 0000000..acc054f --- /dev/null +++ b/simulation/libsimulator/include/simulator/http_server.hpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef HTTP_SERVER_HPP_INCLUDED +#define HTTP_SERVER_HPP_INCLUDED + +#include "simulator/simulator.hpp" +#include + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim +{ + std::string SIMULATOR_DECL trim(std::string s); + + std::string SIMULATOR_DECL lower_case(std::string s); + + std::string SIMULATOR_DECL normalize(const std::string& s); + + // returns the index to the last byte of the request, or -1 if the buffer + // does not contain a full http request + int SIMULATOR_DECL find_request_len(char const* buf, int len); + + struct http_request + { + std::string method; + std::string req; + std::string path; + std::map headers; + }; + + http_request parse_request(char const* start, int len); + + // builds an HTTP response buffer + std::string SIMULATOR_DECL send_response(int code, char const* status_message + , int len = 0, char const** extra_header = NULL); + +// This is a very simple http server that only supports a single concurrent +// connection +struct SIMULATOR_DECL http_server +{ + enum flags_t + { + keep_alive = 1, + // behave like an HTTP/1.0 server: respond with an HTTP/1.0 status line + // and close the connection after each response. HTTP/1.0 has no + // persistent connections, and the Connection header is an HTTP/1.1 + // mechanism, so no "Connection: close" is sent -- the client must detect + // the close from the protocol version (and the socket closing). + http_1_0 = 2 + }; + + http_server(asio::io_context& ios, unsigned short listen_port + , int flags = http_server::keep_alive); + + void stop(); + + using handler_t = std::function&)>; + using generator_t = std::function; + + void register_handler(std::string const& path, handler_t h); + void register_content(std::string const& path + , std::int64_t const size, generator_t gen); + void register_redirect(std::string const& path, std::string const& target); + void register_stall_handler(std::string const& path); + + // the number of TCP connections that have been accepted so far + int accepted_connections() const { return m_accepted_connections; } + +private: + + void on_accept(boost::system::error_code const& ec); + void read(); + void on_read(boost::system::error_code const& ec, size_t bytes_transferred); + void on_write(boost::system::error_code const& ec, size_t bytes_transferred + , bool close); + void close_connection(); + + asio::io_context& m_ios; + + asio::ip::tcp::acceptor m_listen_socket; + + asio::ip::tcp::socket m_connection; + asio::ip::tcp::endpoint m_ep; + + std::unordered_map m_handlers; + std::set m_stall_handlers; + + // read buffer, we receive bytes into this buffer for the connection + std::string m_recv_buffer; + + // the number of bytes of m_recv_buffer that we've actually read data into. + // The remaining is uninitialized, possibly being read into in an async call + int m_bytes_used; + + std::string m_send_buffer; + + // set to true when shutting down + bool m_close; + + int m_flags; + + // counts the number of accepted TCP connections + int m_accepted_connections = 0; +}; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/mallocator.hpp b/simulation/libsimulator/include/simulator/mallocator.hpp new file mode 100644 index 0000000..97baf67 --- /dev/null +++ b/simulation/libsimulator/include/simulator/mallocator.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SIMULATOR_MALLOCATOR_HPP_INCLUDED +#define SIMULATOR_MALLOCATOR_HPP_INCLUDED + +namespace sim +{ +namespace aux +{ + struct channel; + struct packet; + struct pcap; + + template + struct mallocator + { + template + friend struct mallocator; + + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + using reference = T&; + using const_reference = T const&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + template + struct rebind { + using other = mallocator; + }; + + mallocator() = default; + template + mallocator(mallocator const&) {} + + T* allocate(std::size_t size) + { return reinterpret_cast(std::malloc(size * sizeof(T))); } + + void deallocate(T* ptr) { std::free(ptr); } + void deallocate(T* ptr, std::size_t) { std::free(ptr); } + + void destroy(pointer p) { p->~T(); } + void construct(pointer p, T&& value) { new (p) T(std::move(value)); } + + bool operator==(mallocator const&) const { return true; } + bool operator!=(mallocator const&) const { return false; } + }; +} // aux +} // sim + +#endif // SIMULATOR_MALLOCATOR_HPP_INCLUDED + diff --git a/simulation/libsimulator/include/simulator/nat.hpp b/simulation/libsimulator/include/simulator/nat.hpp new file mode 100644 index 0000000..2edebe4 --- /dev/null +++ b/simulation/libsimulator/include/simulator/nat.hpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef NAT_HPP_INCLUDED +#define NAT_HPP_INCLUDED + +#include "simulator/sink.hpp" +#include "simulator/simulator.hpp" +#include + +namespace sim { + +namespace aux { + struct packet; +} + + struct SIMULATOR_DECL nat : sink + { + nat(asio::ip::address external_addr); + ~nat() = default; + + void incoming_packet(aux::packet p) override; + + // used for visualization + std::string label() const override; + + private: + + asio::ip::address m_external_addr; + }; + +} // sim + +#endif + + diff --git a/simulation/libsimulator/include/simulator/noexcept_movable.hpp b/simulation/libsimulator/include/simulator/noexcept_movable.hpp new file mode 100644 index 0000000..32472d3 --- /dev/null +++ b/simulation/libsimulator/include/simulator/noexcept_movable.hpp @@ -0,0 +1,56 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef NOEXCEPT_MOVABLE_HPP_INCLUDED +#define NOEXCEPT_MOVABLE_HPP_INCLUDED + +namespace sim { +namespace aux { + + template + struct noexcept_movable : T + { + noexcept_movable() noexcept {} + noexcept_movable(noexcept_movable&& rhs) noexcept + : T(std::forward(rhs)) + {} + noexcept_movable(noexcept_movable const& rhs) + : T(static_cast(rhs)) + {} + noexcept_movable(T&& rhs) noexcept : T(std::forward(rhs)) {} // NOLINT + noexcept_movable(T const& rhs) : T(rhs) {} // NOLINT + noexcept_movable& operator=(noexcept_movable&& rhs) noexcept + { + this->T::operator=(std::forward(rhs)); + return *this; + } + noexcept_movable& operator=(noexcept_movable const& rhs) + { + this->T::operator=(rhs); + return *this; + } + + using T::T; + using T::operator=; + }; + +} +} + +#endif + diff --git a/simulation/libsimulator/include/simulator/packet.hpp b/simulation/libsimulator/include/simulator/packet.hpp new file mode 100644 index 0000000..f009bb2 --- /dev/null +++ b/simulation/libsimulator/include/simulator/packet.hpp @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PACKET_HPP_INCLUDED +#define PACKET_HPP_INCLUDED + +#include "simulator/config.hpp" +#include "simulator/simulator.hpp" // for route, endpoint + +namespace sim { namespace aux { + + struct channel; + + struct packet + { + packet() = default; + + // this is move-only + packet(packet const&) = delete; + packet& operator=(packet const&) = delete; + packet(packet&&) = default; + packet& operator=(packet&&) = default; + + // to keep things simple, don't drop ACKs or errors + bool ok_to_drop() const + { + return type != type_t::syn_ack + && type != type_t::ack + && type != type_t::error; + } + + enum class type_t + { + uninitialized, // invalid type (used for debugging) + syn, // TCP connect + syn_ack, // TCP connection accepted + ack, // the seq_nr is interpreted as "we received this" + error, // the error_code (ec) is set + payload // the buffer is filled + }; + + type_t type = type_t::uninitialized; + + boost::system::error_code ec; + + // actual payload + std::vector buffer; + + // used for UDP packets + asio::ip::udp::endpoint from; + + // the number of bytes of overhead for this packet. The total packet + // size is the number of bytes in the buffer + this number + int overhead = 20; + + // each hop in the route will pop itself off and forward the packet to + // the next hop + route hops; + + // for SYN packets, this is set to the channel we're trying to + // establish + std::shared_ptr channel; + + // sequence number of this packet (used for debugging) + std::uint64_t seq_nr = 0; + + // the number of (payload) bytes sent over this channel so far. This is + // meant to map to the TCP sequence number + std::uint32_t byte_counter = 0; + + // this function must be called with this packet in case the packet is + // dropped. + aux::function drop_fun; + }; + +}} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/pcap.hpp b/simulation/libsimulator/include/simulator/pcap.hpp new file mode 100644 index 0000000..2719f2e --- /dev/null +++ b/simulation/libsimulator/include/simulator/pcap.hpp @@ -0,0 +1,42 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PCAP_HPP_INCLUDED +#define PCAP_HPP_INCLUDED + +#include +#include "simulator/simulator.hpp" // for endpoint + +namespace sim { namespace aux +{ + struct packet; + + struct pcap + { + pcap(char const* filename); + void log_tcp(packet const& p, asio::ip::tcp::endpoint src + , asio::ip::tcp::endpoint dst); + void log_udp(packet const& p, asio::ip::udp::endpoint src + , asio::ip::udp::endpoint dst); + private: + std::fstream m_file; + }; +}} + +#endif + diff --git a/simulation/libsimulator/include/simulator/pop_warnings.hpp b/simulation/libsimulator/include/simulator/pop_warnings.hpp new file mode 100644 index 0000000..8ffbe4f --- /dev/null +++ b/simulation/libsimulator/include/simulator/pop_warnings.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + diff --git a/simulation/libsimulator/include/simulator/push_warnings.hpp b/simulation/libsimulator/include/simulator/push_warnings.hpp new file mode 100644 index 0000000..1811434 --- /dev/null +++ b/simulation/libsimulator/include/simulator/push_warnings.hpp @@ -0,0 +1,89 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wundef" +#pragma GCC diagnostic ignored "-Wmissing-noreturn" +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wpedantic" +#if __GNUC__ >= 6 +#pragma GCC diagnostic ignored "-Wshift-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +#pragma GCC diagnostic ignored "-Wshift-count-negative" +#endif +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wall" +#pragma clang diagnostic ignored "-Weverything" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wcast-align" +#pragma clang diagnostic ignored "-Wweak-vtable" +#pragma clang diagnostic ignored "-Wundef" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wc++11-long-long" +#pragma clang diagnostic ignored "-Wc++11-extensions" +#pragma clang diagnostic ignored "-Wextra-semi" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#pragma clang diagnostic ignored "-Wgnu-folding-constant" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wfloat-equal" +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +// warning C4005: macro redefinition +#pragma warning( disable : 4005 ) +#endif + diff --git a/simulation/libsimulator/include/simulator/queue.hpp b/simulation/libsimulator/include/simulator/queue.hpp new file mode 100644 index 0000000..2a5f988 --- /dev/null +++ b/simulation/libsimulator/include/simulator/queue.hpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef QUEUE_HPP_INCLUDED +#define QUEUE_HPP_INCLUDED + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/mallocator.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim { + + struct timed_packet + { + timed_packet(chrono::high_resolution_clock::time_point t, aux::packet p) + : ts(t), pkt(std::move(p)) + {} + timed_packet(timed_packet&&) = default; + timed_packet& operator=(timed_packet&&) = default; + timed_packet(timed_packet const&) = delete; + timed_packet& operator=(timed_packet const&) = delete; + chrono::high_resolution_clock::time_point ts; + aux::packet pkt; + }; + + // this is a queue. It can be configured to contrain + struct SIMULATOR_DECL queue : sink + { + queue(asio::io_context& ios, int bandwidth + , chrono::high_resolution_clock::duration propagation_delay + , int max_queue_size, std::string name = "queue"); + + virtual void incoming_packet(aux::packet p) override final; + + virtual std::string label() const override final; + + queue(queue const&) = delete; + queue& operator=(queue const&) = delete; + + queue(queue&&) = default; + queue& operator=(queue&&) = delete; + + private: + + void begin_send_next_packet(); + void next_packet_sent(); + + // the queue can't hold more than this number of bytes. Once it's full, + // any new packets arriving will be dropped (tail drop) + int m_max_queue_size; + + // the amount of time it takes to forward a packet. Every packet is + // delayed by at least this much before being forwarded + chrono::high_resolution_clock::duration m_forwarding_latency; + + // the number of bytes per second that can be sent. This includes the + // packet overhead + int m_bandwidth; + + // the number of bytes currently in the packet queue + int m_queue_size; + + std::string m_node_name; + + // this is the queue of packets and the time each packet was enqueued + std::deque> m_queue; + asio::high_resolution_timer m_forward_timer; + + chrono::high_resolution_clock::time_point m_last_forward; + }; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/simulator.hpp b/simulation/libsimulator/include/simulator/simulator.hpp new file mode 100644 index 0000000..8801ea6 --- /dev/null +++ b/simulation/libsimulator/include/simulator/simulator.hpp @@ -0,0 +1,1486 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SIMULATOR_HPP_INCLUDED +#define SIMULATOR_HPP_INCLUDED + +#include "simulator/push_warnings.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// only for boost::beast::role_type, used by the teardown()/async_teardown() +// overloads below (customization points boost::beast::websocket::stream +// needs for any non-boost::asio socket type it's instantiated over); this +// is the single lightweight header that defines it, not a dependency on +// beast's websocket/http machinery. +#include + +#include + +#include "simulator/pop_warnings.hpp" + +#include "simulator/chrono.hpp" +#include "simulator/sink_forwarder.hpp" +#include "simulator/function.hpp" +#include "simulator/noexcept_movable.hpp" +#include "simulator/mallocator.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef IP_DONTFRAGMENT +#define IP_DONTFRAGMENT 1 +#endif + +namespace sim +{ + namespace aux + { + struct channel; + struct packet; + struct pcap; + + // this is outgoing NIC bandwidth + constexpr int nic_bandwidth = 100000000; // 100 MB/s + } + + // this represents a network route (a series of sinks to pass a packet + // through) + struct SIMULATOR_DECL route + { + friend route operator+(route lhs, route rhs) + { return std::move(lhs.append(std::move(rhs))); } + + std::shared_ptr next_hop() const { return hops.front(); } + std::shared_ptr pop_front() + { + if (hops.empty()) return std::shared_ptr(); + std::shared_ptr ret(std::move(hops.front())); + hops.erase(hops.begin()); + return ret; + } + void replace_last(std::shared_ptr s) { hops.back() = std::move(s); } + void prepend(route const& r) + { hops.insert(hops.begin(), r.hops.begin(), r.hops.end()); } + void prepend(std::shared_ptr s) { hops.insert(hops.begin(), std::move(s)); } + route& append(route const& r) + { hops.insert(hops.end(), r.hops.begin(), r.hops.end()); return *this; } + route& append(std::shared_ptr s) { hops.push_back(std::move(s)); return *this; } + bool empty() const { return hops.empty(); } + std::shared_ptr last() const + { return hops.back(); } + + private: + std::deque, aux::mallocator>> hops; + }; + + void forward_packet(aux::packet p); + + struct simulation; + struct configuration; + struct queue; + + namespace asio + { + + using boost::asio::buffer_size; + using boost::asio::const_buffer; + using boost::asio::mutable_buffer; + using boost::asio::buffer; + + // brought in here (rather than relying on ADL at each call site below) + // so buffer sequence types whose associated namespaces don't include + // boost::asio (e.g. boost::beast's internal buffer sequence types) are + // still found; the "using" also participates in ADL's overload set at + // the point of an unqualified call, so this doesn't shadow lookups for + // types that do live in boost::asio. + using boost::asio::buffer_sequence_begin; + using boost::asio::buffer_sequence_end; + + using boost::asio::post; + using boost::asio::dispatch; + using boost::asio::defer; + + struct io_context; + + struct io_executor + { + io_executor(io_context& ctx) : m_ctx(&ctx) {} + io_context& context() const { return *m_ctx; } + + template + void dispatch(Handler handler, Allocator const& a) const; + + template + void post(Handler handler, Allocator const& a) const; + + template + void defer(Handler handler, Allocator const& a) const; + + void on_work_finished() const {} + void on_work_started() const {} + + bool running_in_this_thread() const { return true; } + + friend bool operator==(io_executor const& lhs, io_executor const& rhs) + { return lhs.m_ctx == rhs.m_ctx; } + + friend bool operator!=(io_executor const& lhs, io_executor const& rhs) + { return lhs.m_ctx != rhs.m_ctx; } + + private: + io_context* m_ctx; + }; + + struct SIMULATOR_DECL high_resolution_timer + { + friend struct sim::simulation; + + using time_type = chrono::high_resolution_clock::time_point; + using duration_type = chrono::high_resolution_clock::duration; + + using executor_type = io_executor; + executor_type get_executor(); + + explicit high_resolution_timer(io_context& io_context); + high_resolution_timer(io_context& io_context, + const time_type& expiry_time); + high_resolution_timer(io_context& io_context, + const duration_type& expiry_time); + high_resolution_timer(high_resolution_timer&&) noexcept = default; + high_resolution_timer& operator=(high_resolution_timer&&) noexcept = default; + ~high_resolution_timer(); + + std::size_t cancel(); + std::size_t cancel_one(); + + time_type expiry() const; + std::size_t expires_at(const time_type& expiry_time); + std::size_t expires_after(const duration_type& expiry_time); + + void wait(); + void wait(boost::system::error_code& ec); + + void async_wait(aux::function handler); + + private: + + void fire(boost::system::error_code ec); + + time_type m_expiration_time; + aux::function m_handler; + io_context* m_io_service; + bool m_expired; + }; + + using waitable_timer = high_resolution_timer; + + namespace error = boost::asio::error; + + template + struct socket_base + { + socket_base(io_context& ios) : m_io_service(ios) {} + socket_base(socket_base&& s) = default; + + enum wait_type_t + { + wait_read, wait_write, wait_error + }; + + // alias matching the name used by boost.asio's basic_socket::wait_type, + // for code that's written against both APIs + using wait_type = wait_type_t; + + // io_control + using reuse_address = boost::asio::socket_base::reuse_address; + using executor_type = io_executor; + executor_type get_executor(); + + // socket options + using send_buffer_size = boost::asio::socket_base::send_buffer_size; + using receive_buffer_size = boost::asio::socket_base::receive_buffer_size; + + template + void set_option(Option const& opt, boost::system::error_code&) + { + Protocol const p = Protocol::v4(); + (void)p; +#ifdef IP_DONTFRAG + if (opt.name(p) == IP_DONTFRAG) + m_dont_fragment = *reinterpret_cast(opt.data(p)) != 0; +#endif +#ifdef IP_DONTFRAGMENT + if (opt.name(p) == IP_DONTFRAGMENT) + m_dont_fragment = *reinterpret_cast(opt.data(p)) != 0; +#endif +#ifdef IP_MTU_DISCOVER + if (opt.name(p) == IP_MTU_DISCOVER) + m_dont_fragment = *reinterpret_cast(opt.data(p)) == IP_PMTUDISC_DO; +#endif + } + + void set_option(receive_buffer_size const& op, boost::system::error_code&) + { + m_max_receive_queue_size = op.value(); + } + + void set_option(send_buffer_size const& op, boost::system::error_code&) + { + // this limit is specified in microseconds. Given the line rate of + // nic_bandwidth, this is the time it takes to send the specified number + // of bytes. + m_send_queue_time = chrono::microseconds(std::int64_t(double(op.value()) * 1000000.0 / double(aux::nic_bandwidth))); + } + + void set_option(reuse_address const&, boost::system::error_code&) + { + // TODO: implement + } + + typename Protocol::endpoint local_endpoint(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = error::bad_descriptor; + return typename Protocol::endpoint{}; + } + + return m_user_bound_to; + } + + typename Protocol::endpoint local_endpoint() const + { + boost::system::error_code ec; + auto const ret = local_endpoint(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + typename Protocol::endpoint local_bound_to(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = error::bad_descriptor; + return typename Protocol::endpoint{}; + } + + return m_bound_to; + } + + typename Protocol::endpoint local_bound_to() const + { + boost::system::error_code ec; + auto const ret = local_bound_to(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + template + void get_option(Option&, boost::system::error_code&) { } + + void get_option(receive_buffer_size& op, boost::system::error_code&) + { + op = m_max_receive_queue_size; + } + + template + void io_control(IoControl const&, boost::system::error_code&) { } + + template + void io_control(IoControl const&) {} + + void non_blocking(bool b, boost::system::error_code&) + { m_non_blocking = b; } + + void non_blocking(bool b) + { m_non_blocking = b; } + + bool is_open() const + { + return m_open; + } + + using message_flags = int; + + // internal interface + + route get_incoming_route(); + route get_outgoing_route(); + + protected: + + io_context& m_io_service; + + typename Protocol::endpoint m_bound_to; + + // this is the interface the user requested to bind to (in order to + // distinguish the concrete interface it was bound to and INADDR_ANY if + // that was requested). We keep this separately to return it as the local + // endpoint + typename Protocol::endpoint m_user_bound_to; + + // this is an object implementing the sink interface, forwarding + // packets to this socket. If this socket is destructed, this forwarder + // is redirected to just drop packets. This is necessary since sinks + // must be held by shared_ptr, and socket objects aren't. + std::shared_ptr m_forwarder; + + // whether the socket is open or not + bool m_open = false; + + // true if the socket is set to non-blocking mode + bool m_non_blocking = false; + + // when true, the MTU limit is in effect + bool m_dont_fragment = false; + + // the max size of the incoming queue. This is to emulate the send and + // receive buffer. This should also depend on the bandwidth, to not + // make the queue size not grow too long in time. + int m_max_receive_queue_size = 64 * 1024; + + // the number of microseconds worth of send buffer this socket has, at + // NIC linerate. + chrono::microseconds m_send_queue_time{200000}; + }; + + namespace ip { + + using boost::asio::ip::address; + using boost::asio::ip::address_v4; + using boost::asio::ip::address_v6; + + using boost::asio::ip::make_address_v4; + using boost::asio::ip::make_address_v6; + using boost::asio::ip::make_address; + + using boost::asio::ip::make_network_v4; + + template + struct basic_endpoint : boost::asio::ip::basic_endpoint + { + basic_endpoint(ip::address const& addr, unsigned short port) + : boost::asio::ip::basic_endpoint(addr, port) {} + basic_endpoint() : boost::asio::ip::basic_endpoint() {} + }; + + template + struct basic_resolver_entry + { + using endpoint_type = typename Protocol::endpoint; + using protocol_type = Protocol; + + basic_resolver_entry() {} + basic_resolver_entry( + endpoint_type const& ep + , std::string const& host + , std::string const& service) + : m_endpoint(ep) + , m_host_name(host) + , m_service(service) + {} + + endpoint_type endpoint() const { return m_endpoint; } + std::string host_name() const { return m_host_name; } + operator endpoint_type() const { return m_endpoint; } + std::string service_name() const { return m_service; } + + private: + endpoint_type m_endpoint; + std::string m_host_name; + std::string m_service; + }; + + template + struct SIMULATOR_DECL basic_resolver + { + basic_resolver(io_context& ios); + + using protocol_type = Protocol; + using results_type = std::vector, aux::mallocator>>; + + void cancel(); + + void async_resolve(std::string hostname, char const* service + , aux::function handler); + + basic_resolver(basic_resolver&&) noexcept; + basic_resolver& operator=(basic_resolver&&) noexcept; + basic_resolver(basic_resolver const&) = delete; + basic_resolver& operator=(basic_resolver const&) = delete; + + //TODO: add remaining members + + private: + + void on_lookup(boost::system::error_code const& ec); + + struct result_t + { + chrono::high_resolution_clock::time_point completion_time; + boost::system::error_code err; + results_type ips; + aux::function handler; + + result_t( + chrono::high_resolution_clock::time_point ct + , boost::system::error_code e + , results_type ips_ + , aux::function h) + : completion_time(ct) + , err(e) + , ips(std::move(ips_)) + , handler(std::move(h)) + {} + + result_t(result_t&&) noexcept = default; + result_t& operator=(result_t&&) = default; + result_t(result_t const&) = delete; + result_t& operator=(result_t const&) = delete; + }; + + io_context* m_ios; + asio::high_resolution_timer m_timer; + using queue_t = aux::noexcept_movable>; + + queue_t m_queue; + }; + + struct SIMULATOR_DECL udp + { + static udp v4() { return udp(AF_INET); } + static udp v6() { return udp(AF_INET6); } + + using endpoint = basic_endpoint; + + struct SIMULATOR_DECL socket : socket_base, sink + { + using endpoint_type = ip::udp::endpoint; + using protocol_type = ip::udp; + using lowest_layer_type = socket; + + socket(io_context& ios); + ~socket() override; + + socket(socket const&) = delete; + socket& operator=(socket const&) = delete; + socket(socket&&); + + lowest_layer_type& lowest_layer() { return *this; } + + void bind(ip::udp::endpoint const& ep + , boost::system::error_code& ec); + void bind(ip::udp::endpoint const& ep); + + void close(); + void close(boost::system::error_code& ec); + + void cancel(boost::system::error_code& ec); + void cancel(); + + void open(udp protocol, boost::system::error_code& ec); + void open(udp protocol); + + template + std::size_t send_to(ConstBufferSequence const& bufs + , udp::endpoint const& destination + , socket_base::message_flags flags + , boost::system::error_code& ec) + { + std::vector b(buffer_sequence_begin(bufs) + , buffer_sequence_end(bufs)); + abort_send_handlers(); + return send_to_impl(b, destination, flags, ec); + } + + template + std::size_t send_to(ConstBufferSequence const& bufs + , udp::endpoint const& destination) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_send_handlers(); + boost::system::error_code ec; + std::size_t ret = send_to_impl(b, destination, 0, ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + void async_wait(socket_base::wait_type_t w + , aux::function handler); + + template + void async_receive(BufferSequence const& bufs + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_receive_from_impl(b, nullptr, 0, std::move(handler)); + } + + template + void async_receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_receive_from_impl(b, &sender, 0, std::move(handler)); + } + + template + void async_receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , socket_base::message_flags flags + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_receive_from_impl(b, &sender, flags, std::move(handler)); + } +/* + void async_read_from(null_buffers const& + , aux::function handler) + { + abort_recv_handlers(); + async_read_some_null_buffers_impl(std::move(handler)); + } +*/ + + template + std::size_t receive_from(BufferSequence const& bufs + , udp::endpoint& sender) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + assert(!b.empty()); + abort_recv_handlers(); + boost::system::error_code ec; + std::size_t ret = receive_from_impl(b, &sender, 0, ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + template + std::size_t receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , socket_base::message_flags) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + assert(!b.empty()); + abort_recv_handlers(); + boost::system::error_code ec; + std::size_t ret = receive_from_impl(b, &sender, 0, ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + template + std::size_t receive_from(BufferSequence const& bufs + , udp::endpoint& sender + , socket_base::message_flags + , boost::system::error_code& ec) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + assert(!b.empty()); + abort_recv_handlers(); + return receive_from_impl(b, &sender, 0, ec); + } + + // TODO: support connect and remote_endpoint + + // internal interface + + // implements sink + virtual void incoming_packet(aux::packet p) override final; + virtual std::string label() const override final + { return m_bound_to.address().to_string(); } + + void async_receive_from_impl(std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags flags + , aux::function handler); + + std::size_t receive_from_impl( + std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags flags + , boost::system::error_code& ec); + + void async_wait_receive_impl( + udp::endpoint* sender + , aux::function handler); + + private: + + void maybe_wakeup_reader(); + void abort_send_handlers(); + void abort_recv_handlers(); + + std::size_t send_to_impl(std::vector const& b + , udp::endpoint const& dst, message_flags flags + , boost::system::error_code& ec); + + // this is the next time we'll have an opportunity to send another + // outgoing packet. This is used to implement the bandwidth constraints + // of channels. This may be in the past, in which case it's OK to send + // a packet immediately. + chrono::high_resolution_clock::time_point m_next_send; + + // while we're blocked in an async_write_some operation, this is the + // handler that should be called once we're done sending + aux::function + m_send_handler; + aux::function + m_wait_send_handler; + + // if we have an outstanding read on this socket, this is set to the + // handler. + aux::function + m_recv_handler; + aux::function + m_wait_recv_handler; + + // if we have an outstanding read operation, this is the buffer to + // receive into + std::vector m_recv_buffer; + + // if we have an outstanding receive operation, this may point to an + // endpoint to fill in the senders IP in + udp::endpoint* m_recv_sender; + + asio::high_resolution_timer m_recv_timer; + asio::high_resolution_timer m_send_timer; + + // this is the incoming queue of packets for each socket + std::list> m_incoming_queue; + + bool m_recv_null_buffers; + + // the number of bytes in the incoming packet queue + int m_queue_size; + + // our address family + bool m_is_v4; + }; + + using resolver = basic_resolver; + + int family() const { return m_family; } + + friend bool operator==(udp const& lhs, udp const& rhs) + { return lhs.m_family == rhs.m_family; } + + friend bool operator!=(udp const& lhs, udp const& rhs) + { return lhs.m_family != rhs.m_family; } + + private: + // Construct with a specific family. + explicit udp(int protocol_family) + : m_family(protocol_family) + {} + + int m_family; + + }; // udp + + struct SIMULATOR_DECL tcp + { + // temporary fix until the resolvers are implemented using our endpoint + tcp(boost::asio::ip::tcp p) : m_family(p.family()) {} + + static tcp v4() { return tcp(AF_INET); } + static tcp v6() { return tcp(AF_INET6); } + + int family() const { return m_family; } + + using endpoint = basic_endpoint; + + struct SIMULATOR_DECL socket : socket_base, sink + { + using endpoint_type = ip::tcp::endpoint; + using protocol_type = ip::tcp; + using lowest_layer_type = socket; + + explicit socket(io_context& ios); + socket(socket const&) = delete; + socket& operator=(socket const&) = delete; + socket(socket&&); + socket& operator=(socket&&); + + ~socket() override; + + void close(); + void close(boost::system::error_code& ec); + void open(tcp protocol, boost::system::error_code& ec); + void open(tcp protocol); + void bind(ip::tcp::endpoint const& ep + , boost::system::error_code& ec); + void bind(ip::tcp::endpoint const& ep); + tcp::endpoint remote_endpoint(boost::system::error_code& ec) const; + tcp::endpoint remote_endpoint() const; + + lowest_layer_type& lowest_layer() { return *this; } + + void async_connect(tcp::endpoint const& target + , aux::function h); + + template + void async_write_some(ConstBufferSequence const& bufs + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_send_handlers(); + async_write_some_impl(b, std::move(handler)); + } + + void async_wait(socket_base::wait_type_t const w + , aux::function handler) + { + if (w == socket_base::wait_type_t::wait_write) + { + abort_send_handlers(); + async_wait_write_impl(std::move(handler)); + } + else if (w == socket_base::wait_type_t::wait_read) + { + abort_recv_handlers(); + async_wait_read_impl(std::move(handler)); + } + } + + template + std::size_t read_some(BufferSequence const& bufs + , boost::system::error_code& ec) + { + assert(m_non_blocking && "blocking operations not supported"); + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + return read_some_impl(b, ec); + } + + template + std::size_t write_some(ConstBufferSequence const& bufs + , boost::system::error_code& ec) + { + assert(m_non_blocking && "blocking operations not supported"); + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + return write_some_impl(b, ec); + } + + template + void async_read_some(BufferSequence const& bufs + , aux::function handler) + { + std::vector b(buffer_sequence_begin(bufs), buffer_sequence_end(bufs)); + abort_recv_handlers(); + + async_read_some_impl(b, std::move(handler)); + } + + std::size_t available(boost::system::error_code & ec) const; + std::size_t available() const; + + void cancel(boost::system::error_code& ec); + void cancel(); + + using socket_base::set_option; + using socket_base::get_option; + using socket_base::io_control; + + // private interface + + // implements sink + virtual void incoming_packet(aux::packet p) override; + virtual std::string label() const override final + { return m_bound_to.address().to_string(); } + + void internal_connect(tcp::endpoint const& bind_ip + , std::shared_ptr const& c + , boost::system::error_code& ec); + + void abort_send_handlers(); + void abort_recv_handlers(); + + virtual bool internal_is_listening(); + protected: + + void maybe_wakeup_reader(); + void maybe_wakeup_writer(); + + void async_write_some_impl(std::vector const& bufs + , aux::function handler); + void async_read_some_impl(std::vector const& bufs + , aux::function handler); + void async_wait_read_impl( + aux::function handler); + void async_wait_write_impl( + aux::function handler); + std::size_t write_some_impl(std::vector const& bufs + , boost::system::error_code& ec); + std::size_t read_some_impl(std::vector const& bufs + , boost::system::error_code& ec); + + void send_packet(aux::packet p); + + // called when a packet is dropped + void packet_dropped(aux::packet p); + + aux::function m_connect_handler; + + asio::high_resolution_timer m_connect_timer; + + // the tcp "packet size" (segment size) + // TODO: name this constant! + int m_mss = 1475; + + // while we're blocked in an async_write_some operation, this is the + // handler that should be called once we're done sending + aux::function m_send_handler; + aux::function m_wait_send_handler; + + std::vector m_send_buffer; + + // this is the incoming queue of packets for each socket + std::list> m_incoming_queue; + + // the number of bytes in the incoming packet queue + int m_queue_size = 0; + + // if we have an outstanding read on this socket, this is set to the + // handler. + aux::function m_recv_handler; + aux::function m_wait_recv_handler; + + // if we have an outstanding buffer to receive into, these are them + std::vector m_recv_buffer; + + asio::high_resolution_timer m_recv_timer; + + // our address family + bool m_is_v4 = true; + + // true if the currently outstanding read operation is for null_buffers + bool m_recv_null_buffers = false; + + // true if the currenly outstanding write operation is for null_buffers + bool m_send_null_buffers = false; + + // if this socket is connected to another endpoint, this object is + // shared between both sockets and contain information and state about + // the channel. + std::shared_ptr m_channel; + + std::uint64_t m_next_outgoing_seq = 0; + std::uint64_t m_next_incoming_seq = 0; + + // the sequence number of the last dropped packet. We should only cut + // the cwnd in half once per round-trip. If a whole window is lost, we + // need to only halve it once + std::uint64_t m_last_drop_seq = 0; + + // the current congestion window size (in bytes) + int m_cwnd = m_mss * 2; + + // the number of bytes that have been sent but not ACKed yet + int m_bytes_in_flight = 0; + + // reorder buffer for when packets are dropped + std::map, aux::mallocator>> m_reorder_buffer; + + // the sizes of packets given their sequence number + std::unordered_map, std::equal_to, aux::mallocator>> m_outstanding_packet_sizes; + + // packets to re-send (because they were dropped) + std::list> m_outgoing_packets; + }; + + struct SIMULATOR_DECL acceptor : socket + { + explicit acceptor(io_context& ios); + acceptor(acceptor&&); + ~acceptor() override; + + void cancel(boost::system::error_code& ec); + void cancel(); + + void listen(int qs = -1); + void listen(int qs, boost::system::error_code& ec); + + void async_accept(ip::tcp::socket& peer + , aux::function h); + void async_accept(ip::tcp::socket& peer + , ip::tcp::endpoint& peer_endpoint + , aux::function h); + void async_accept(aux::function h); + + void close(boost::system::error_code& ec); + void close(); + + // private interface + + // implements sink + virtual void incoming_packet(aux::packet p) override final; + virtual bool internal_is_listening() override final; + + private: + // check the incoming connection queue to see if any connection in + // there is ready to be accepted and delivered to the user + void check_accept_queue(); + void do_check_accept_queue(boost::system::error_code const& ec); + + aux::function m_accept_handler; + aux::function m_accept_handler2; + + // the number of half-open incoming connections this listen socket can + // hold. If this is -1, this socket is not yet listening and incoming + // connection attempts should be rejected. + int m_queue_size_limit; + + // these are incoming connection attempts. Both half-open and + // completely connected. When accepting a connection, this queue is + // checked first before waiting for a connection attempt. + using incoming_conns_t = std::vector, aux::mallocator>>; + incoming_conns_t m_incoming_conns; + + // for new-style accept, allocate socket in here just to fail early + boost::optional m_new_socket; + + // the socket to accept a connection into + tcp::socket* m_accept_into; + + // the endpoint to write the remote endpoint into when accepting + tcp::endpoint* m_remote_endpoint; + + // non copyable + acceptor(acceptor const&); + acceptor& operator=(acceptor const&); + }; + + using resolver = basic_resolver; + + friend bool operator==(tcp const& lhs, tcp const& rhs) + { return lhs.m_family == rhs.m_family; } + + friend bool operator!=(tcp const& lhs, tcp const& rhs) + { return lhs.m_family != rhs.m_family; } + + private: + // Construct with a specific family. + explicit tcp(int protocol_family) + : m_family(protocol_family) + {} + + int m_family; + }; + + extern template struct basic_resolver; + extern template struct basic_resolver; + + // boost::beast looks up a close function for a socket-like type via + // unqualified lookup + ADL under the name "beast_close_socket" (see + // boost/beast/core/stream_traits.hpp); this satisfies that customization + // point for tcp::socket - it's just a free function beast's ADL-based + // lookup will find because it lives in tcp::socket's own namespace, so + // beast::websocket::stream doesn't need this header to + // declare it inside boost::beast itself. + inline void beast_close_socket(tcp::socket& s) + { + boost::system::error_code ec; + s.close(ec); + } + + // same idea for the teardown()/async_teardown() customization points + // boost::beast::websocket::stream looks up (via ADL, from + // boost::beast::websocket) for any Socket type it's instantiated over + // that isn't a boost::asio socket -- see + // boost/beast/websocket/teardown.hpp. tcp::socket has no half-close/ + // non-blocking read-drain support to mirror the real graceful-shutdown + // sequence those implement for a real socket, so this just closes it; + // that's sufficient for driving deterministic test scenarios, which is + // the only thing this socket type is ever used for. + inline void teardown(boost::beast::role_type, tcp::socket& s, boost::system::error_code& ec) + { + s.close(ec); + } + + template + void async_teardown(boost::beast::role_type, tcp::socket& s, Handler&& handler) + { + boost::system::error_code ec; + s.close(ec); + boost::asio::post(s.get_executor(), std::bind(std::forward(handler), ec)); + } + + } // ip + + // mirrors (a subset of) boost::asio::async_connect(socket, begin, end, + // handler): tries each endpoint in the range in turn, closing and + // advancing to the next on failure, until one connects or the range is + // exhausted. Calls handler(error_code, Iterator) with the iterator that + // succeeded, or the end iterator carrying the last endpoint's error if + // all of them failed. Lives directly in sim::asio, alongside the + // boost::asio free function it mirrors, rather than in sim::asio::ip, so + // that unqualified calls resolve to whichever of the two applies via + // ADL (ip::tcp::socket's base, socket_base, is declared in this + // namespace, which is what makes that lookup work). + template + void async_connect(ip::tcp::socket& s, Iterator begin, Iterator end, Handler handler) + { + struct op : std::enable_shared_from_this + { + op(ip::tcp::socket& sock, Iterator b, Iterator e, Handler h) + : socket(sock), iter(b), end(e), handler(std::move(h)) + {} + + void attempt() + { + socket.async_connect(*iter, [self = this->shared_from_this()] + (boost::system::error_code const& ec) { self->on_connect(ec); }); + } + + void on_connect(boost::system::error_code const& ec) + { + if (ec && std::next(iter) != end) + { + boost::system::error_code ignore; + socket.close(ignore); + ++iter; + attempt(); + return; + } + + handler(ec, iter); + } + + ip::tcp::socket& socket; + Iterator iter; + Iterator end; + Handler handler; + }; + + if (begin == end) + { + handler(boost::asio::error::make_error_code(boost::asio::error::not_found), end); + return; + } + + std::make_shared(s, begin, end, std::move(handler))->attempt(); + } + + } // asio + + struct SIMULATOR_DECL simulation + { + // it calls fire() when a timer fires + friend struct high_resolution_timer; + + simulation(configuration& config); + ~simulation(); + + std::size_t run(); + + std::size_t poll(boost::system::error_code& ec); + std::size_t poll(); + + std::size_t poll_one(boost::system::error_code& ec); + std::size_t poll_one(); + + void stop(); + bool stopped() const; + void restart(); + // private interface + + void add_timer(asio::high_resolution_timer* t); + void remove_timer(asio::high_resolution_timer* t); + + boost::asio::io_context& get_internal_service() + { return m_service; } + + asio::io_context& get_io_context() + { return *m_internal_ios; } + + asio::ip::tcp::endpoint bind_socket(asio::ip::tcp::socket* socket + , asio::ip::tcp::endpoint ep + , boost::system::error_code& ec); + void unbind_socket(asio::ip::tcp::socket* socket + , asio::ip::tcp::endpoint const& ep); + void rebind_socket(asio::ip::tcp::socket* prev, asio::ip::tcp::socket* s, asio::ip::tcp::endpoint ep); + + asio::ip::udp::endpoint bind_udp_socket(asio::ip::udp::socket* socket + , asio::ip::udp::endpoint ep + , boost::system::error_code& ec); + void unbind_udp_socket(asio::ip::udp::socket* socket + , asio::ip::udp::endpoint const& ep); + void rebind_udp_socket(asio::ip::udp::socket* socket, asio::ip::udp::endpoint ep); + + std::shared_ptr internal_connect(asio::ip::tcp::socket* s + , asio::ip::tcp::endpoint const& target, boost::system::error_code& ec); + + route find_udp_socket( + asio::ip::udp::socket const& socket + , asio::ip::udp::endpoint const& ep); + + configuration& config() const { return m_config; } + + void add_io_service(asio::io_context* ios); + void remove_io_service(asio::io_context* ios); + std::vector get_all_io_services() const; + + aux::pcap* get_pcap() const { return m_pcap.get(); } + void log_pcap(char const* filename); + + private: + struct timer_compare + { + bool operator()(asio::high_resolution_timer const* lhs + , asio::high_resolution_timer const* rhs) const + { return lhs->expiry() < rhs->expiry(); } + }; + + configuration& m_config; + + std::unique_ptr m_pcap; + + // all non-expired timers + std::mutex m_timer_queue_mutex; + using timer_queue_t = std::vector>; + timer_queue_t m_timer_queue; + + // these are the io services that represent nodes on the network + std::unordered_set, std::equal_to, aux::mallocator> m_nodes; + + using listen_sockets_t = std::map; + using listen_socket_iter_t = listen_sockets_t::iterator; + listen_sockets_t m_listen_sockets; + + using udp_sockets_t = std::map; + using udp_socket_iter_t = udp_sockets_t::iterator; + udp_sockets_t m_udp_sockets; + + // used for internal timers. this is a pimpl sine our io_context is + // incomplete at this point + std::unique_ptr m_internal_ios; + + // the next port to use for an outgoing connection, where the port is not + // specified. We want this to be as unique as possible, to distinguish the + // TCP streams. + std::uint16_t m_next_bind_port = 2000; + + bool m_stopped = false; + + // underlying message queue + boost::asio::io_context m_service; + }; + + namespace asio { + + using boost::asio::async_write; + using boost::asio::async_read; + + // boost.asio compatible io_context class that simulates the network + // and time. + struct SIMULATOR_DECL io_context : boost::asio::execution_context + { + io_context(sim::simulation& sim); + io_context(sim::simulation& sim, ip::address const& ip); + io_context(sim::simulation& sim, std::vector const& ips); + io_context(std::size_t threads_hint = 0); + ~io_context(); + + // not copyable and non movable (it's not movable because we currently + // keep pointers to the io_context instances in the simulator object) + io_context(io_context const&) = delete; + io_context(io_context&&) = delete; + io_context& operator=(io_context const&) = delete; + io_context& operator=(io_context&&) = delete; + + std::size_t run(boost::system::error_code& ec); + std::size_t run(); + + std::size_t poll(boost::system::error_code& ec); + std::size_t poll(); + + std::size_t poll_one(boost::system::error_code& ec); + std::size_t poll_one(); + + template + std::size_t run_for(std::chrono::duration const&) + { + assert(false); + return 0; + } + + template + std::size_t poll_one_for(std::chrono::duration const&) + { + assert(false); + return 0; + } + + void stop(); + bool stopped() const; + void restart(); + + template + void dispatch_impl(Handler handler, Allocator const& a) + { get_internal_service().get_executor().dispatch(std::move(handler), a); } + + template + void post_impl(Handler handler, Allocator const& a) + { get_internal_service().get_executor().post(std::move(handler), a); } + + template + void defer_impl(Handler handler, Allocator const& a) + { get_internal_service().get_executor().defer(std::move(handler), a); } + + // internal interface + boost::asio::io_context& get_internal_service(); + + void add_timer(high_resolution_timer* t); + void remove_timer(high_resolution_timer* t); + + ip::tcp::endpoint bind_socket(ip::tcp::socket* socket, ip::tcp::endpoint ep + , boost::system::error_code& ec); + void unbind_socket(ip::tcp::socket* socket + , ip::tcp::endpoint const& ep); + void rebind_socket(asio::ip::tcp::socket* prev, asio::ip::tcp::socket* s, asio::ip::tcp::endpoint ep); + + ip::udp::endpoint bind_udp_socket(ip::udp::socket* socket, ip::udp::endpoint ep + , boost::system::error_code& ec); + void unbind_udp_socket(ip::udp::socket* socket + , ip::udp::endpoint const& ep); + void rebind_udp_socket(asio::ip::udp::socket* socket, asio::ip::udp::endpoint ep); + + std::shared_ptr internal_connect(ip::tcp::socket* s + , ip::tcp::endpoint const& target, boost::system::error_code& ec); + + route find_udp_socket(asio::ip::udp::socket const& socket + , ip::udp::endpoint const& ep); + + route const& get_outgoing_route(ip::address ip) const + { return m_outgoing_route.find(ip)->second; } + + route const& get_incoming_route(ip::address ip) const + { return m_incoming_route.find(ip)->second; } + + int get_path_mtu(const asio::ip::address& source, const asio::ip::address& dest) const; + std::vector const& get_ips() const { return m_ips; } + + sim::simulation& sim() { return m_sim; } + + using executor_type = io_executor; + executor_type get_executor() { return executor_type(*this); } + + private: + + sim::simulation& m_sim; + std::vector m_ips; + + // these are determined by the configuration. They may include NATs and + // DSL modems (queues) + std::map m_outgoing_route; + std::map m_incoming_route; + + bool m_stopped = false; + }; + + template + void io_executor::dispatch(Handler handler, Allocator const& a) const + { m_ctx->dispatch_impl(std::move(handler), a); } + + template + void io_executor::post(Handler handler, Allocator const& a) const + { m_ctx->post_impl(std::move(handler), a); } + + template + void io_executor::defer(Handler handler, Allocator const& a) const + { m_ctx->defer_impl(std::move(handler), a); } + + template + io_executor + socket_base::get_executor() + { return io_executor(m_io_service); } + + template + route socket_base::get_incoming_route() + { + route ret = m_io_service.get_incoming_route(m_bound_to.address()); + assert(m_forwarder); + ret.append(std::static_pointer_cast(m_forwarder)); + return ret; + } + + template + route socket_base::get_outgoing_route() + { + return route(m_io_service.get_outgoing_route(m_bound_to.address())); + } + + } // asio + + // user supplied configuration of the network to simulate + struct SIMULATOR_DECL configuration + { + virtual ~configuration() {} + + // build the network + virtual void build(simulation& sim) = 0; + + // return the hops on the network packets from src to dst need to traverse + virtual route channel_route(asio::ip::address src + , asio::ip::address dst) = 0; + + // return the hops an incoming packet to ep need to traverse before + // reaching the socket (for instance a NAT) + virtual route incoming_route(asio::ip::address ip) = 0; + + // return the hops an outgoing packet from ep need to traverse before + // reaching the network (for instance a DSL modem) + virtual route outgoing_route(asio::ip::address ip) = 0; + + // return the path MTU between the two IP addresses + // For TCP sockets, this will be called once when the connection is + // established. For UDP sockets it's called for every burst of packets + // that are sent + virtual int path_mtu(asio::ip::address ip1, asio::ip::address ip2) = 0; + + // called for every hostname lookup made by the client. ``reqyestor`` is + // the node performing the lookup, ``hostname`` is the name being looked + // up. Resolve the name into addresses and fill in ``result`` or set + // ``ec`` if the hostname is not found or some other error occurs. The + // return value is the latency of the lookup. The client's callback won't + // be called until after waiting this long. + virtual chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) = 0; + + virtual void clear() = 0; + }; + + struct SIMULATOR_DECL default_config : configuration + { + default_config() : m_sim(nullptr) {} + + void build(simulation& sim) override; + route channel_route(asio::ip::address src, asio::ip::address dst) override; + route incoming_route(asio::ip::address ip) override; + route outgoing_route(asio::ip::address ip) override; + int path_mtu(asio::ip::address ip1, asio::ip::address ip2) override; + chrono::high_resolution_clock::duration hostname_lookup( + asio::ip::address const& requestor + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) override; + + void clear() override; + protected: + std::shared_ptr m_network; + std::map> m_incoming; + std::map> m_outgoing; + simulation* m_sim; + }; + + namespace aux + { + /* the channel can be in the following states: + 1. handshake-1 - the initiating socket has sent SYN + 2. handshake-2 - the accepting connection has sent SYN+ACK + 3. handshake-3 - the initiating connection has received the SYN+ACK and + considers the connection open, but the 3rd handshake + message is still in flight. + 4. connected - the accepting side has received the 3rd handshake + packet and considers it open + + Whenever a connection attempt is made to a listening socket, as long as + there is still space in the incoming socket queue, the accepting side + will always respond immediately and complete the handshake, then wait + until the user calls async_accept (which in this case would complete + immediately). + */ + struct SIMULATOR_DECL channel + { + channel() {} + // index 0 is the incoming route to the socket that initiated the connection. + // index 1 may be empty while the connection is half-open + route hops[2]; + + // the actual endpoint of each end of the channel + asio::ip::tcp::endpoint ep[2]; + + // observable endpoint of each side of the channel. This is not how you + // see yourself, just the other end + asio::ip::tcp::endpoint visible_ep[2]; + + // the number of bytes sent from respective direction + // this is used to simulate the TCP sequence number, so it deliberately + // is meant to wrap at 32 bits + std::uint32_t bytes_sent[2]; + + int remote_idx(asio::ip::tcp::endpoint const& self) const; + int self_idx(asio::ip::tcp::endpoint const& self) const; + }; + + } // aux + + void SIMULATOR_DECL dump_network_graph(simulation const& s, const std::string& filename); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // SIMULATOR_HPP_INCLUDED + diff --git a/simulation/libsimulator/include/simulator/sink.hpp b/simulation/libsimulator/include/simulator/sink.hpp new file mode 100644 index 0000000..35f19b3 --- /dev/null +++ b/simulation/libsimulator/include/simulator/sink.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SINK_HPP_INCLUDED +#define SINK_HPP_INCLUDED + +#include "simulator/config.hpp" +#include + +namespace sim { + +namespace aux { + struct packet; +} + + // this is an interface for somthing that can accept incoming packets, + // such as queues, sockets, NATs and TCP congestion windows + struct SIMULATOR_DECL sink + { + virtual void incoming_packet(aux::packet p) = 0; + + // used for visualization + virtual std::string label() const = 0; + + virtual std::string attributes() const { return "shape=box"; } + virtual ~sink() = default; + }; + +} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/sink_forwarder.hpp b/simulation/libsimulator/include/simulator/sink_forwarder.hpp new file mode 100644 index 0000000..318f0a8 --- /dev/null +++ b/simulation/libsimulator/include/simulator/sink_forwarder.hpp @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SINK_FORWARDER_HPP_INCLUDED +#define SINK_FORWARDER_HPP_INCLUDED + +#include "simulator/config.hpp" +#include "simulator/sink.hpp" + +namespace sim { namespace aux { + + struct packet; + + struct SIMULATOR_DECL sink_forwarder final : sink + { + sink_forwarder(sink* dst); + void incoming_packet(packet p) override; + std::string label() const override; + void reset(sink* s = nullptr); + + private: + sink* m_dst; + }; + +}} // sim + +#endif + diff --git a/simulation/libsimulator/include/simulator/socks_server.hpp b/simulation/libsimulator/include/simulator/socks_server.hpp new file mode 100644 index 0000000..fb37397 --- /dev/null +++ b/simulation/libsimulator/include/simulator/socks_server.hpp @@ -0,0 +1,202 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SOCKS_SERVER_HPP_INCLUDED +#define SOCKS_SERVER_HPP_INCLUDED + +#include "simulator/simulator.hpp" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#if __GNUC__ >= 9 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif +#endif + +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(push) +// warning C4251: X: class Y needs to have dll-interface to be used by clients of struct +#pragma warning( disable : 4251) +#endif + +namespace sim +{ + +enum socks_flag +{ + // when this flag is set, the proxy will close the client connection + // immediately after sending the response to a UDP ASSOCIATE command + disconnect_udp_associate = 1, + + // when this flag is set, the reponse to UDP ASSOCIATE will contain an empty + // hostname, rather than the relay IP address + udp_associate_respond_empty_hostname = 2 +}; + +struct SIMULATOR_DECL socks_connection : std::enable_shared_from_this +{ + socks_connection(asio::io_context& ios, int version, std::array& cmd_counts + , std::uint32_t flags, int& bind_port); + + asio::ip::tcp::socket& socket() { return m_client_connection; } + + void start(); + +private: + + void on_handshake1(boost::system::error_code const& ec, size_t bytes_transferred); + void on_handshake2(boost::system::error_code const& ec, size_t bytes_transferred); + void on_handshake3(boost::system::error_code const& ec, size_t bytes_transferred); + void on_request1(boost::system::error_code const& ec, size_t bytes_transferred); + void on_request2(boost::system::error_code const& ec, size_t bytes_transferred); + + void on_write(boost::system::error_code const& ec, size_t bytes_transferred + , bool close); + void close_connection(); + + int format_response(asio::ip::address const& addr, int port, int response); + int format_hostname_response(char const* hostname, int port, int response); + + void on_connected(boost::system::error_code const& ec); + void on_request_domain_name(boost::system::error_code const& ec, size_t bytes_transferred); + void on_request_domain_lookup(boost::system::error_code const& ec + , const asio::ip::tcp::resolver::results_type ips); + + void open_forward_connection(asio::ip::tcp::endpoint const& target); + void bind_connection(asio::ip::tcp::endpoint const& target); + void start_accept(boost::system::error_code const& ec); + + void udp_associate(asio::ip::tcp::endpoint const& target); + void on_read_udp(boost::system::error_code const& ec, std::size_t bytes_transferred); + void wait_for_eof(boost::system::error_code const& ec, std::size_t bytes_transferred); + + void on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred); + void on_server_forward(boost::system::error_code const& ec + , size_t bytes_transferred); + + void on_client_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred); + void on_client_forward(boost::system::error_code const& ec + , size_t bytes_transferred); + + char const* command() const; + + int& m_bind_port; + + asio::io_context& m_ios; + + asio::ip::udp::resolver m_udp_resolver; + + asio::ip::tcp::resolver m_resolver; + + boost::bimap m_name_mapping; + + // this is the SOCKS client connection, i.e. the client connecting to us and + // being forwarded + asio::ip::tcp::socket m_client_connection; + + // this is the connection to the server the socks client is being forwarded + // to + asio::ip::tcp::socket m_server_connection; + asio::ip::tcp::acceptor m_bind_socket; + + asio::ip::udp::socket m_udp_associate; + asio::ip::udp::endpoint m_udp_associate_ep; + asio::ip::udp::endpoint m_udp_from; + + std::array m_udp_buffer; + + // receive buffer for data going out, i.e. client -> proxy (us) -> server + char m_out_buffer[65536]; + // buffer size + int m_num_out_bytes; + + // receive buffer for data coming in, i.e. server -> proxy (us) -> client + char m_in_buffer[65536]; + // buffer size + int m_num_in_bytes; + + // set to true when shutting down + bool m_close = false; + + // the SOCKS protocol version (4 or 5) + const int m_version; + + int m_command; + + std::array& m_cmd_counts; + + std::uint32_t const m_flags; +}; + +// This is a very simple socks4 and 5 server that only supports a single +// concurrent connection +struct SIMULATOR_DECL socks_server +{ + socks_server(asio::io_context& ios, unsigned short listen_port + , int version = 5, std::uint32_t flags = 0); + + void stop(); + + void bind_start_port(int const port) { m_bind_port = port; } + + // return the number of CONNECT, BIND and UDP_ASSOCIATE commands the proxy + // has received + std::array cmd_counts() const + { return m_cmd_counts; } + +private: + + void on_accept(boost::system::error_code const& ec); + + asio::io_context& m_ios; + + asio::ip::tcp::acceptor m_listen_socket; + + std::shared_ptr m_conn; + + asio::ip::tcp::endpoint m_ep; + + int m_bind_port = 2048; + + // set to true when shutting down + bool m_close = false; + + // the SOCKS protocol version (4 or 5) + const int m_version; + + std::array m_cmd_counts; + + std::uint32_t const m_flags; +}; + +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif + diff --git a/simulation/libsimulator/include/simulator/ssl.hpp b/simulation/libsimulator/include/simulator/ssl.hpp new file mode 100644 index 0000000..9e47936 --- /dev/null +++ b/simulation/libsimulator/include/simulator/ssl.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2026, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SIMULATOR_SSL_HPP_INCLUDED +#define SIMULATOR_SSL_HPP_INCLUDED + +#include "simulator/simulator.hpp" + +#include "simulator/push_warnings.hpp" +#include +#include "simulator/pop_warnings.hpp" + +namespace sim { +namespace asio { + +namespace ssl +{ + using boost::asio::ssl::context; + using boost::asio::ssl::stream_base; + using boost::asio::ssl::verify_context; + + template + using stream = boost::asio::ssl::stream; +} // ssl + +} // asio +} // sim + +#endif diff --git a/simulation/libsimulator/include/simulator/utils.hpp b/simulation/libsimulator/include/simulator/utils.hpp new file mode 100644 index 0000000..a45bb03 --- /dev/null +++ b/simulation/libsimulator/include/simulator/utils.hpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef UTILS_HPP_INCLUDED +#define UTILS_HPP_INCLUDED + +#include "simulator/simulator.hpp" + +namespace sim +{ + +// shortcut for creating a timer with a timeout and action +struct timer +{ + timer(simulation& sim, chrono::high_resolution_clock::duration timeout + , aux::function&& f) + : m_ios(sim, asio::ip::address_v4()) + , m_timer(m_ios) + { + m_timer.expires_after(timeout); + m_timer.async_wait(std::move(f)); + } + +private: + sim::asio::io_context m_ios; + sim::asio::high_resolution_timer m_timer; +}; + +} // sim + +#endif + diff --git a/simulation/libsimulator/src/acceptor.cpp b/simulation/libsimulator/src/acceptor.cpp new file mode 100644 index 0000000..c937628 --- /dev/null +++ b/simulation/libsimulator/src/acceptor.cpp @@ -0,0 +1,379 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" + +#include +#include + +#include + +using boost::beast::bind_handler; + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim { +namespace asio { +namespace ip { + + tcp::acceptor::acceptor(io_context& ios) + : socket(ios) + , m_queue_size_limit(-1) + {} + + tcp::acceptor::acceptor(acceptor&&) = default; + + tcp::acceptor::~acceptor() + { + boost::system::error_code ec; + close(ec); + } + + void tcp::acceptor::listen(int qs) + { + boost::system::error_code ec; + listen(qs, ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::acceptor::listen(int qs, boost::system::error_code& ec) + { + if (qs == -1) qs = 20; + + if (!m_open) + { + ec = error::bad_descriptor; + return; + } + if (m_bound_to == ip::tcp::endpoint()) + { + ec = error::invalid_argument; + return; + } + + m_queue_size_limit = qs; + ec.clear(); + } + + void tcp::acceptor::close(boost::system::error_code& ec) + { + m_queue_size_limit = -1; + cancel(ec); + socket::close(ec); + } + + void tcp::acceptor::close() + { + if (m_accept_handler) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + } + + void tcp::acceptor::cancel(boost::system::error_code& ec) + { + ec.clear(); + if (m_accept_handler) + { + try + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + catch (std::bad_alloc const&) + { + ec = error::no_memory; + } + catch (std::exception const&) + { + ec = error::no_memory; + } + } + if (m_accept_handler2) + { + try + { + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + catch (std::bad_alloc const&) + { + ec = error::no_memory; + } + catch (std::exception const&) + { + ec = error::no_memory; + } + } + } + + void tcp::acceptor::cancel() + { + boost::system::error_code ec; + cancel(ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::acceptor::async_accept(ip::tcp::socket& peer + , aux::function h) + { + // TODO: assert that the io_context we use is the same as the one peer use + if (peer.is_open()) + { + boost::system::error_code ec; + peer.close(ec); + } + + if (m_accept_handler) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + m_accept_handler = std::move(h); + m_accept_into = &peer; + m_remote_endpoint = nullptr; + + check_accept_queue(); + } + + void tcp::acceptor::async_accept(ip::tcp::socket& peer + , ip::tcp::endpoint& peer_endpoint + , aux::function h) + { + if (peer.is_open()) + { + boost::system::error_code ec; + peer.close(ec); + } + + if (m_accept_handler) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + m_accept_handler = std::move(h); + m_accept_into = &peer; + m_remote_endpoint = &peer_endpoint; + + check_accept_queue(); + } + + void tcp::acceptor::async_accept(aux::function h) + { + m_remote_endpoint = nullptr; + if (m_accept_handler) + { + m_accept_into = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + m_new_socket.emplace(m_io_service); + m_accept_handler2 = std::move(h); + m_accept_into = &*m_new_socket; + + check_accept_queue(); + } + + void tcp::acceptor::do_check_accept_queue(boost::system::error_code const& ec) + { + if (ec) return; + check_accept_queue(); + } + + void tcp::acceptor::incoming_packet(aux::packet p) + { + switch (p.type) + { + case aux::packet::type_t::syn: + m_incoming_conns.push_back(p.channel); + check_accept_queue(); + return; + case aux::packet::type_t::error: + assert(false); // something is not wired up correctly + if (m_accept_handler) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + return; + default: + // if this happens, it implies that an incoming connection sent + // payload before receiving a syn_ack. Alternatively that the + // acceptor sent the syn_ack but still left the last-hop in the + // incoming route to point to this socket, instead of the + // accepted-into socket + assert(false); + return; + } + } + + void tcp::acceptor::check_accept_queue() + { + if (!is_open()) + { + // if the acceptor socket is closed. Any potential socket in the queue + // should be closed too. + for (auto const& incoming : m_incoming_conns) + { + aux::packet p; + p.from = asio::ip::udp::endpoint( + m_bound_to.address(), m_bound_to.port()); + p.type = aux::packet::type_t::error; + p.ec = boost::system::error_code(error::connection_reset); + p.overhead = 28; + p.hops = incoming->hops[0]; + + forward_packet(std::move(p)); + } + m_incoming_conns.clear(); + + if (m_accept_handler) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr) + , boost::system::error_code(error::operation_aborted))); + } + if (m_accept_handler2) + { + m_accept_into = nullptr; + m_remote_endpoint = nullptr; + post(m_io_service, [&, h = std::exchange(m_accept_handler2, nullptr)] () mutable { + h(boost::system::error_code(error::operation_aborted) + , ip::tcp::socket(m_io_service)); + }); + } + } + + // if the user is not waiting for an incoming connection, there's no point + // in checking the queue + if (!m_accept_handler && !m_accept_handler2) return; + + if (m_incoming_conns.empty()) return; + + std::shared_ptr c = std::move(m_incoming_conns.front()); + m_incoming_conns.erase(m_incoming_conns.begin()); + + // this was initiated at least one 3-way handshake ago. + // we can pick it up and consider it connected + if (m_remote_endpoint) *m_remote_endpoint = c->ep[0]; + m_remote_endpoint = nullptr; + + boost::system::error_code ec; + // if the acceptor socket is closed. Any potential socket in the queue + m_accept_into->internal_connect(m_bound_to, c, ec); + + // notify the other end + aux::packet p; + p.from = asio::ip::udp::endpoint(m_bound_to.address(), m_bound_to.port()); + if (ec) + { + c->hops[1] = route(); + p.type = aux::packet::type_t::error; + p.ec = ec; + } + else + { + // TODO: extend pcap logging to include SYN+ACK packets + p.type = aux::packet::type_t::syn_ack; + } + p.channel = c; + p.overhead = 28; + p.hops = p.channel->hops[0]; + + forward_packet(std::move(p)); + + try + { + if (m_accept_handler) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler, nullptr), ec)); + } + else if (m_accept_handler2) + { + post(m_io_service, bind_handler(std::exchange(m_accept_handler2, nullptr) + , ec, std::move(*m_accept_into))); + } + } + catch (...) + { + m_new_socket.reset(); + m_accept_into = nullptr; + throw; + } + m_new_socket.reset(); + m_accept_into = nullptr; + } + + bool tcp::acceptor::internal_is_listening() + { + return m_queue_size_limit > 0; + } +} +} +} + diff --git a/simulation/libsimulator/src/default_config.cpp b/simulation/libsimulator/src/default_config.cpp new file mode 100644 index 0000000..934983f --- /dev/null +++ b/simulation/libsimulator/src/default_config.cpp @@ -0,0 +1,106 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/queue.hpp" +#include + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; +using sim::asio::ip::address_v4; +using sim::asio::ip::address_v6; +using sim::asio::ip::address; +using sim::chrono::milliseconds; +using sim::chrono::duration_cast; + +namespace sim { + + void default_config::build(simulation& sim) + { + // 0 bandwidth and 0 queue means infinite. The network itself only adds + // 50 ms latency + m_network = std::make_shared(std::ref(sim.get_io_context()) + , 0, duration_cast(milliseconds(30)), 0, "network"); + m_sim = ∼ + } + + void default_config::clear() + { + m_network.reset(); + m_outgoing.clear(); + m_incoming.clear(); + } + + route default_config::channel_route( + asio::ip::address /* src */ + , asio::ip::address /* dst */) + { + return route().append(m_network); + } + + route default_config::incoming_route(asio::ip::address ip) + { + // incoming download rate is 800kB/s with a 200 kB queue + // and 1 ms forwarding delay + auto it = m_incoming.find(ip); + if (it != m_incoming.end()) return route().append(it->second); + it = m_incoming.insert(it, std::make_pair(ip, std::make_shared( + std::ref(m_sim->get_io_context()), 800 * 1000 + , duration_cast(milliseconds(1)), 200 * 1000, "DSL modem in"))); + return route().append(it->second); + } + + int default_config::path_mtu( + asio::ip::address /* ip1 */ + , asio::ip::address /* ip2 */) + { + return 1475; + } + + // return the hops an outgoing packet from ep need to traverse before + // reaching the network (for instance a DSL modem) + route default_config::outgoing_route(asio::ip::address ip) + { + // outgoing upload rate is 200kB/s with a 200 kB queue + // and 1 ms forwarding delay + auto it = m_outgoing.find(ip); + if (it != m_outgoing.end()) return route().append(it->second); + it = m_outgoing.insert(it, std::make_pair(ip, std::make_shared( + std::ref(m_sim->get_io_context()), 200 * 1000 + , duration_cast(milliseconds(1)), 200 * 1000, "DSL modem out"))); + return route().append(it->second); + } + + duration default_config::hostname_lookup( + asio::ip::address const& /* requestor */ + , std::string hostname + , std::vector& result + , boost::system::error_code& ec) + { + if (hostname == "localhost") + { + result = { asio::ip::make_address_v6("::1") + , asio::ip::make_address_v4("127.0.0.1") }; + return duration_cast(chrono::microseconds(1)); + } + + ec = boost::system::error_code(asio::error::host_not_found); + return duration_cast(chrono::milliseconds(100)); + } +} + diff --git a/simulation/libsimulator/src/high_resolution_clock.cpp b/simulation/libsimulator/src/high_resolution_clock.cpp new file mode 100644 index 0000000..c942efc --- /dev/null +++ b/simulation/libsimulator/src/high_resolution_clock.cpp @@ -0,0 +1,51 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" + +#include + +#include "simulator/push_warnings.hpp" +#include +#include "simulator/pop_warnings.hpp" + +namespace sim { namespace chrono { + namespace { + + // this is the global simulation timer + high_resolution_clock::time_point g_simulation_time; + } + + high_resolution_clock::time_point high_resolution_clock::now() + { + return g_simulation_time; + } + + void high_resolution_clock::fast_forward(high_resolution_clock::duration d) + { + g_simulation_time += d; + } + + void reset_clock() + { + g_simulation_time = high_resolution_clock::time_point{}; + } + +} // chrono +} // sim + diff --git a/simulation/libsimulator/src/high_resolution_timer.cpp b/simulation/libsimulator/src/high_resolution_timer.cpp new file mode 100644 index 0000000..6542248 --- /dev/null +++ b/simulation/libsimulator/src/high_resolution_timer.cpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/handler_allocator.hpp" + +#include +#include + +namespace sim +{ + + namespace asio { + + high_resolution_timer::high_resolution_timer(io_context& ioc) + : m_expiration_time(time_type()) + , m_io_service(&ioc) + , m_expired(true) + { + } + + high_resolution_timer::high_resolution_timer(io_context& ioc, + const time_type& expiry_time) + : m_expiration_time(time_type()) + , m_io_service(&ioc) + , m_expired(true) + { + expires_at(expiry_time); + } + + high_resolution_timer::high_resolution_timer(io_context& ioc, + const duration_type& expiry_time) + : m_expiration_time(time_type()) + , m_io_service(&ioc) + , m_expired(true) + { + expires_after(expiry_time); + } + + high_resolution_timer::~high_resolution_timer() + { + cancel(); + } + + std::size_t high_resolution_timer::cancel() + { + if (m_expired) return 0; + m_expired = true; + m_io_service->remove_timer(this); + if (!m_handler) return 0; + fire(boost::asio::error::operation_aborted); + return 1; + } + + std::size_t high_resolution_timer::cancel_one() + { + // TODO: support multiple handlers + return cancel(); + } + + high_resolution_timer::time_type high_resolution_timer::expiry() const + { return m_expiration_time; } + + std::size_t high_resolution_timer::expires_at(high_resolution_timer::time_type const& expiry_time) + { + std::size_t ret = cancel(); + m_expiration_time = expiry_time; + m_expired = false; + m_io_service->add_timer(this); + return ret; + } + + std::size_t high_resolution_timer::expires_after(const duration_type& expiry_time) + { + std::size_t ret = cancel(); + m_expiration_time = chrono::high_resolution_clock::now() + expiry_time; + m_expired = false; + m_io_service->add_timer(this); + return ret; + } + + void high_resolution_timer::wait() + { + assert(false && "can't use synchcronous calls in simulator"); + boost::system::error_code ec; + wait(ec); + } + + void high_resolution_timer::wait(boost::system::error_code&) + { + assert(false && "can't use synchcronous calls in simulator"); + time_type now = chrono::high_resolution_clock::now(); + if (now >= m_expiration_time) return; + chrono::high_resolution_clock::fast_forward(m_expiration_time - now); + } + + void high_resolution_timer::async_wait(aux::function handler) + { + // TODO: support multiple handlers + assert(!m_handler); + m_handler = std::move(handler); + if (m_expired) + { + fire(boost::system::error_code()); + return; + } + } + + void high_resolution_timer::fire(boost::system::error_code ec) + { + m_expired = true; + if (!m_handler) return; + auto h = std::move(m_handler); + m_handler = nullptr; + post(*m_io_service, make_malloc(std::bind(std::move(h), ec))); + } + + high_resolution_timer::executor_type high_resolution_timer::get_executor() + { + return (*m_io_service).get_executor(); + } + + } // asio + +} // sim + diff --git a/simulation/libsimulator/src/http_proxy.cpp b/simulation/libsimulator/src/http_proxy.cpp new file mode 100644 index 0000000..82aa80d --- /dev/null +++ b/simulation/libsimulator/src/http_proxy.cpp @@ -0,0 +1,362 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/http_proxy.hpp" +#include "simulator/http_server.hpp" // for helper functions + +#include +#include // for printf + +using namespace sim::asio; +using namespace sim::asio::ip; +using namespace std::placeholders; + +using boost::system::error_code; + +namespace sim +{ + using namespace aux; + + http_proxy::http_proxy(io_context& ios, unsigned short const listen_port) + : m_resolver(ios) + , m_listen_socket(ios) + , m_client_connection(ios) + , m_server_connection(ios) + , m_writing_to_server(false) + , m_num_client_in_bytes(0) + , m_num_server_out_bytes(0) + , m_num_in_bytes(0) + , m_close(false) + { + address local_ip = ios.get_ips().front(); + if (local_ip.is_v4()) + { + m_listen_socket.open(tcp::v4()); + m_listen_socket.bind(tcp::endpoint(address_v4::any(), listen_port)); + } + else + { + m_listen_socket.open(tcp::v6()); + m_listen_socket.bind(tcp::endpoint(address_v6::any(), listen_port)); + } + m_listen_socket.listen(); + + m_listen_socket.async_accept(m_client_connection, m_ep + , std::bind(&http_proxy::on_accept, this, _1)); + } + + void http_proxy::on_accept(error_code const& ec) + { + if (ec == asio::error::operation_aborted) + return; + + if (ec) + { + std::printf("http_proxy::on_accept: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + std::printf("http_proxy accepted connection from: %s : %d\n", + m_ep.address().to_string().c_str(), m_ep.port()); + + // read http request + m_client_connection.async_read_some(asio::buffer( + &m_client_in_buffer[0], sizeof(m_client_in_buffer)) + , std::bind(&http_proxy::on_read_request, this, _1, _2)); + } + + void http_proxy::on_read_request(error_code const& ec, size_t bytes_transferred) try + { + if (ec) + { + std::printf("http_proxy::on_read_request: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_num_client_in_bytes += int(bytes_transferred); + + // scan for end of HTTP request + int req_len = find_request_len(m_client_in_buffer, m_num_client_in_bytes); + while (req_len >= 0) + { + // parse request from [0, eor), connect to target server and forward + // the request. + http_request const req = parse_request(m_client_in_buffer, req_len); + forward_request(req); + + // pop this request from the receive buffer + memmove(m_client_in_buffer, m_client_in_buffer + req_len + , m_num_client_in_bytes - req_len); + m_num_client_in_bytes -= req_len; + + // is there another request in the buffer? + req_len = find_request_len(m_client_in_buffer, m_num_client_in_bytes); + } + + // read more from the client + m_client_connection.async_read_some(asio::buffer( + &m_client_in_buffer[m_num_client_in_bytes] + , sizeof(m_client_in_buffer) - m_num_client_in_bytes) + , std::bind(&http_proxy::on_read_request, this, _1, _2)); + } + catch (std::runtime_error& e) + { + std::printf("http_proxy::on_read_request() failed: %s\n" + , e.what()); + close_connection(); + } + + void http_proxy::forward_request(http_request const& req) + { + std::string out_request; + out_request = req.method; + out_request += ' '; + if (req.req.compare(0, 7, "http://") != 0) + { + std::printf("http_proxy::forward_request: expected full URL in request, got: %s\n" + , req.req.c_str()); + throw std::runtime_error("invalid request"); + } + + std::string::size_type const path_start = req.req.find_first_of('/', 7); + if (path_start == std::string::npos) out_request += '/'; + else out_request.append(req.req, path_start, std::string::npos); + out_request += " HTTP/1.1\r\n"; + + std::string::size_type const host_end = req.req.substr(0, path_start).find_last_of(':'); + + std::string host = req.req.substr(7, (host_end != std::string::npos && host_end > 7) + ? host_end - 7 : path_start - 7); + + // if the hostname is an IPv6 address, strip the brackets around it to + // make it parse correctly + if (host.size() >= 2 && host.front() == '[' && host.back() == ']') + host = host.substr(1, host.size() - 2); + + int const port = host_end == std::string::npos || host_end <= 7 ? 80 + : atoi(req.req.substr(host_end + 1, path_start).c_str()); + assert(port >= 0 && port < 0xffff); + + bool found_host = false; + for (auto const& h : req.headers) + { + if (h.first == "host") found_host = true; + + out_request += h.first; + out_request += ": "; + out_request += h.second; + out_request += "\r\n"; + } + if (!found_host) + { + out_request += "host: "; + out_request += host; + out_request += "\r\n"; + } + out_request += "\r\n"; + + if (m_num_server_out_bytes + out_request.size() > sizeof(m_server_out_buffer)) + { + std::printf("http_proxy: Too many queued server requests: %d bytes\n" + , int(m_num_server_out_bytes + out_request.size())); + throw std::runtime_error("pipeline too deep"); + } + memmove(&m_server_out_buffer[m_num_server_out_bytes] + , out_request.data(), out_request.size()); + m_num_server_out_bytes += int(out_request.size()); + + if (!m_server_connection.is_open()) + { + boost::system::error_code err; + tcp::endpoint target(make_address(host.c_str(), err) + , static_cast(port)); + if (err) + { + char port_str[10]; + std::snprintf(port_str, sizeof(port_str), "%d", port); + m_resolver.async_resolve(host, port_str + , std::bind(&http_proxy::on_domain_lookup, this, _1, _2)); + return; + } + open_forward_connection(target); + return; + } + + // TODO: make sure we're connecting/connected to the same (host, port) + // that this request is for. Don't support multiple servers + + write_server_send_buffer(); + } + + void http_proxy::on_domain_lookup(boost::system::error_code const& ec + , const asio::ip::tcp::resolver::results_type ips) + { + if (ec || ips.empty()) + { + if (ec) + { + std::printf("http_proxy::on_domain_lookup: (%d) %s\n" + , ec.value(), ec.message().c_str()); + } + else + { + std::printf("http_proxy::on_request_domain_lookup: empty response\n"); + } + error(503, "Resource Temporarily Unavailable"); + return; + } + open_forward_connection(ips.front().endpoint()); + } + + void http_proxy::open_forward_connection(const asio::ip::tcp::endpoint& target) + { + m_server_connection.open(target.protocol()); + + std::printf("http_proxy: async_connect: %s:%d\n" + , target.address().to_string().c_str(), target.port()); + m_server_connection.async_connect(target + , std::bind(&http_proxy::on_connected, this, _1)); + } + + void http_proxy::error(int code, char const* message) + { + std::string send_buffer = send_response(code, message); + memcpy(m_in_buffer, send_buffer.data(), send_buffer.size()); + asio::async_write(m_client_connection, asio::buffer( + &m_in_buffer[0], send_buffer.size()) + , std::bind(&http_proxy::close_connection, this)); + } + + void http_proxy::on_connected(boost::system::error_code const& ec) + { + if (ec) + { + std::printf("http_proxy::on_connected() connection failed: %s\n", ec.message().c_str()); + m_server_connection.close(); + error(503, "Service Temporarily Unavailable"); + return; + } + + std::printf("http_proxy: connected\n"); + + write_server_send_buffer(); + + m_server_connection.async_read_some( + asio::buffer(m_in_buffer, sizeof(m_in_buffer)) + , std::bind(&http_proxy::on_server_receive, this, _1, _2)); + } + + void http_proxy::write_server_send_buffer() + { + if (m_writing_to_server) return; + m_writing_to_server = true; + m_server_connection.async_write_some(asio::buffer( + &m_server_out_buffer[0], m_num_server_out_bytes) + , std::bind(&http_proxy::on_server_write, this, _1, _2)); + } + + void http_proxy::on_server_write(error_code const& ec, size_t bytes_transferred) + { + m_writing_to_server = false; + if (ec) + { + std::printf("http_proxy::on_server_write: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + memmove(&m_server_out_buffer[0], &m_server_out_buffer[bytes_transferred] + , m_num_server_out_bytes - bytes_transferred); + m_num_server_out_bytes -= int(bytes_transferred); + + if (m_num_server_out_bytes > 0) + write_server_send_buffer(); + } + + // we received some data from the server, forward it to the server + void http_proxy::on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + if (ec) + { + std::printf("http_proxy: error reading from server: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], bytes_transferred) + , std::bind(&http_proxy::on_server_forward, this, _1, _2)); + } + + void http_proxy::on_server_forward(error_code const& ec + , size_t) + { + if (ec) + { + std::printf("http_proxy: error writing to client: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_server_connection.async_read_some( + sim::asio::buffer(m_in_buffer, sizeof(m_in_buffer)) + , std::bind(&http_proxy::on_server_receive, this, _1, _2)); + } + + void http_proxy::stop() + { + m_close = true; + m_listen_socket.close(); + } + + void http_proxy::close_connection() + { + m_num_client_in_bytes = 0; + m_num_server_out_bytes = 0; + m_num_in_bytes = 0; + + error_code err; + m_client_connection.close(err); + if (err) + { + std::printf("http_proxy::close: failed to close client connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + m_server_connection.close(err); + if (err) + { + std::printf("http_proxy::close: failed to close server connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + + if (m_close) return; + + // now we can accept another connection + m_listen_socket.async_accept(m_client_connection, m_ep + , std::bind(&http_proxy::on_accept, this, _1)); + } +} + diff --git a/simulation/libsimulator/src/http_server.cpp b/simulation/libsimulator/src/http_server.cpp new file mode 100644 index 0000000..55cad35 --- /dev/null +++ b/simulation/libsimulator/src/http_server.cpp @@ -0,0 +1,425 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/http_server.hpp" + +#include +#include // for printf + +using namespace sim::asio; +using namespace sim::asio::ip; +using namespace std::placeholders; + +using boost::system::error_code; + +namespace sim +{ + using namespace aux; + + namespace { + char const* find(char const* hay, int const hsize + , char const* needle, int const nsize) + { + for (int i = 0; i < hsize - nsize + 1; ++i) + { + if (memcmp(hay + i, needle, nsize) == 0) return hay + i; + } + return nullptr; + } + } + + std::string trim(std::string s) + { + if (s.empty()) return s; + + int start = 0; + int end = int(s.size()); + while (strchr(" \r\n\t", s[start]) != NULL && start < end) + { + ++start; + } + + while (strchr(" \r\n\t", s[end-1]) != NULL && end > start) + { + --end; + } + return s.substr(start, end - start); + } + + std::string lower_case(std::string s) + { + std::string ret; + std::transform(s.begin(), s.end(), std::back_inserter(ret) + , [](char c) { return static_cast(tolower(c)); } ); + return ret; + } + + std::string normalize(const std::string& s) + { + std::vector elements; + char const* start = s.c_str(); + if (*start == '/') ++start; + char const* slash = strchr(start, '/'); + while (slash != NULL) + { + std::string element(start, slash - start); + if (element != "..") + { + elements.push_back(element); + } else if (!elements.empty()) + { + elements.erase(elements.end()-1); + } + start = slash + 1; + slash = strchr(start, '/'); + } + elements.push_back(start); + + std::string ret; + for (auto const& e : elements) + { + ret += '/'; + ret += e; + } + + return ret; + } + + // TODO: extra_header should be a std::vector + std::string send_response(int code, char const* status_message + , int len, char const** extra_header) + { + std::string ret = "HTTP/1.1 " + std::to_string(code) + " " + status_message + "\r\n"; + + ret += "content-length: " + std::to_string(len) + "\r\n"; + + if (extra_header) + { + ret += extra_header[0]; + ret += extra_header[1]; + ret += extra_header[2]; + ret += extra_header[3]; + } + ret += "\r\n"; + + return ret; + } + + http_server::http_server(io_context& ios, unsigned short listen_port, int flags) + : m_ios(ios) + , m_listen_socket(ios) + , m_connection(ios) + , m_bytes_used(0) + , m_close(false) + , m_flags(flags) + { + address local_ip = ios.get_ips().front(); + if (local_ip.is_v4()) + { + m_listen_socket.open(tcp::v4()); + m_listen_socket.bind(tcp::endpoint(address_v4::any(), listen_port)); + } + else + { + m_listen_socket.open(tcp::v6()); + m_listen_socket.bind(tcp::endpoint(address_v6::any(), listen_port)); + } + m_listen_socket.listen(); + + m_listen_socket.async_accept(m_connection, m_ep + , std::bind(&http_server::on_accept, this, _1)); + } + + void http_server::on_accept(error_code const& ec) + { + if (ec) + { + std::printf("http_server::on_accept: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + ++m_accepted_connections; + + std::printf("http_server accepted connection from: %s : %d\n", + m_ep.address().to_string().c_str(), m_ep.port()); + + read(); + } + + void http_server::register_handler(std::string const& path, handler_t h) + { + m_handlers[path] = std::move(h); + } + + void http_server::register_content(std::string const& path + , std::int64_t const size, generator_t gen) + { + m_handlers[path] = [gen,size](std::string, std::string, std::map& hdr) + { + std::int64_t start = 0; + std::int64_t end = size; + + auto it = hdr.find("range"); + bool const range_req = it != hdr.end(); + if (range_req) + { + std::string range = it->second; + // skip "bytes " + range = range.substr(range.find_first_of('=') + 1); + start = std::stoll(range.substr(0, range.find('-'))); + end = std::stoll(range.substr(range.find_first_of('-') + 1)) + 1; + } + + std::string header = "Content-Range: bytes " + std::to_string(start) + + "-" + std::to_string(end-1) + "/" + std::to_string(end-start) + "\r\n"; + char const* extra_headers[4] = { header.c_str(), "", "", ""}; + + return sim::send_response(range_req ? 206 : 200 + , range_req ? "Partial Content" : "OK" + , int(end - start), range_req ? extra_headers : nullptr) + + gen(start, end - start); + }; + } + + void http_server::register_redirect(std::string const& path, std::string const& target) + { + m_handlers[path] = [target](std::string, std::string, std::map&) + { + std::string header = "Location: " + target + "\r\n"; + char const* extra_headers[4] = { header.c_str(), "", "", ""}; + return sim::send_response(301, "Moved Permanently", 0, extra_headers); + }; + } + + void http_server::register_stall_handler(std::string const& path){ + m_stall_handlers.insert(path); + } + + void http_server::read() + { + if (m_bytes_used >= int(m_recv_buffer.size()) / 2) + { + m_recv_buffer.resize((std::max)(500, m_bytes_used * 2)); + } + assert(int(m_recv_buffer.size()) > m_bytes_used); + m_connection.async_read_some(asio::buffer(&m_recv_buffer[m_bytes_used] + , m_recv_buffer.size() - m_bytes_used) + , std::bind(&http_server::on_read, this, _1, _2)); + } + + http_request parse_request(char const* start, int len) + { + http_request ret; + + char const* const end_of_request = start + len; + char const* const space = find(start, len, " ", 1); + if (space == nullptr) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + + char const* const space2 = find(space + 1 + , int(len - (space - start + 1)), " ", 1); + if (space2 == nullptr) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + ret.method.assign(start, space); + ret.req.assign(space+1, space2); + if (ret.method != "CONNECT") { + ret.path.assign(normalize(ret.req.substr(0, ret.req.find_first_of('?')))); + } else { + ret.path.assign(ret.req); + } + std::printf("parse_request: %s %s [%s]\n" + , ret.method.c_str(), ret.path.c_str(), ret.req.c_str()); + + char const* header = find(space2, int(len - (space2 - start)), "\r\n", 2); + while (header != end_of_request - 4) + { + if (header == nullptr) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + char const* const next = find(header + 2 + , int(len - (header + 2 - start)), "\r\n", 2); + char const* const value = static_cast(memchr(header, ':', len - (header - start))); + if (value == nullptr || next == nullptr || value > next) + { + std::printf("http_server: failed to parse request:\n%s\n" + , std::string(start, len).c_str()); + throw std::runtime_error("parse failed"); + } + + ret.headers[lower_case(trim(std::string(header, value)))] + = trim(std::string(value+1, next)); + + header = next; + } + return ret; + } + + int find_request_len(char const* buf, int const len) + { + char const* end_of_request = find(buf, len, "\r\n\r\n", 4); + if (end_of_request == nullptr) return -1; + return int(end_of_request - buf + 4); + } + + void http_server::on_read(error_code const& ec, size_t bytes_transferred) try + { + if (ec) + { + std::printf("http_server::on_read: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_bytes_used += int(bytes_transferred); + + int const req_len = find_request_len(m_recv_buffer.data(), m_bytes_used); + if (req_len < 0) + { + read(); + return; + } + + http_request req = parse_request(m_recv_buffer.data(), req_len); + + m_recv_buffer.erase(m_recv_buffer.begin(), m_recv_buffer.begin() + req_len); + m_bytes_used -= req_len; + + auto it = m_handlers.find(req.path); + if (it == m_handlers.end()) + { + if (m_stall_handlers.find(req.path) != m_stall_handlers.end()) + { + return; + } + // no handler found, 404 + m_send_buffer = send_response(404, "Not Found"); + } + else + { + m_send_buffer = it->second(req.method, req.req, req.headers); + } + + // decide whether to close the connection after this response, and signal + // it to the client appropriately. + bool close; + if (m_flags & http_1_0) + { + // an HTTP/1.0 server closes after every response and does not use the + // Connection header (that is an HTTP/1.1 mechanism). Downgrade the + // status line so the client detects this from the protocol version. + close = true; + auto const ver = m_send_buffer.find("HTTP/1.1"); + if (ver != std::string::npos) + m_send_buffer.replace(ver, 8, "HTTP/1.0"); + } + else + { + // close if the client asked us to, or if this server is not + // configured for keep-alive. When we do, advertise it with a + // "Connection: close" response header so the client knows not to + // reuse the socket (rather than discovering it via a failed write). + close = lower_case(req.headers["connection"]) == "close" + || !(m_flags & keep_alive); + if (close) + { + auto const status_end = m_send_buffer.find("\r\n"); + if (status_end != std::string::npos) + { + assert(m_send_buffer.find("Connection:") == std::string::npos); + m_send_buffer.insert(status_end + 2, "Connection: close\r\n"); + } + } + } + + async_write(m_connection, asio::buffer(m_send_buffer.data() + , m_send_buffer.size()), std::bind(&http_server::on_write + , this, _1, _2, close)); + } + catch (std::exception& e) + { + std::printf("http_server::on_read() failed: %s\n" + , e.what()); + close_connection(); + } + + void http_server::on_write(error_code const& ec + , size_t /* bytes_transferred */ + , bool close) + { + if (ec) + { + std::printf("http_server::on_write: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + if (!close) + { + // try to read another request out of the buffer + post(m_ios, std::bind(&http_server::on_read, this, error_code(), 0)); + } + else + { + close_connection(); + } + } + + void http_server::stop() + { + m_close = true; + m_listen_socket.close(); + } + + void http_server::close_connection() + { + m_recv_buffer.clear(); + m_bytes_used = 0; + + error_code err; + m_connection.close(err); + if (err) + { + std::printf("http_server::close: failed to close connection (%d) %s\n" + , err.value(), err.message().c_str()); + return; + } + + if (m_close) return; + + // now we can accept another connection + m_listen_socket.async_accept(m_connection, m_ep + , std::bind(&http_server::on_accept, this, _1)); + } +} + diff --git a/simulation/libsimulator/src/io_service.cpp b/simulation/libsimulator/src/io_service.cpp new file mode 100644 index 0000000..049c957 --- /dev/null +++ b/simulation/libsimulator/src/io_service.cpp @@ -0,0 +1,272 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" + +#include +#include + +namespace sim { namespace asio { + + io_context::io_context(sim::simulation& sim) + : io_context(sim, std::vector()) + {} + + io_context::io_context(sim::simulation& sim, asio::ip::address const& ip) + : io_context(sim, std::vector{ip}) + {} + + io_context::io_context(sim::simulation& sim, std::vector const& ips) + : m_sim(sim) + , m_ips(ips) + , m_stopped(false) + { + for (auto const& ip : m_ips) + { + m_outgoing_route[ip] = m_sim.config().outgoing_route(ip); + m_incoming_route[ip] = m_sim.config().incoming_route(ip); + } + m_sim.add_io_service(this); + } + + io_context::~io_context() + { + m_sim.remove_io_service(this); + } + + // this constructor is never meant to be called (hence the assert). It only + // exists to satisfy interfaces that require a default-like constructor. The + // null reference is deliberate, so suppress the warning about it. +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnull-dereference" +#elif defined __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + io_context::io_context(std::size_t) + : m_sim(*reinterpret_cast(0)) + { + assert(false); + } +#if defined __clang__ +#pragma clang diagnostic pop +#elif defined __GNUC__ +#pragma GCC diagnostic pop +#endif + + int io_context::get_path_mtu(const asio::ip::address& source, const asio::ip::address& dest) const + { + // TODO: it would be nice to actually traverse the virtual network nodes + // and ask for their MTU instead + assert(std::count(m_ips.begin(), m_ips.end(), source) > 0 && "source address must be a local address to this node/io_context"); + return m_sim.config().path_mtu(source, dest); + } + + void io_context::stop() + { + // TODO: cancel all outstanding handler associated with this io_context + m_stopped = true; + } + + bool io_context::stopped() const + { + return m_stopped; + } + + void io_context::restart() + { + m_stopped = false; + } + + std::size_t io_context::run() + { + assert(false); + return 0; + } + + std::size_t io_context::run(boost::system::error_code&) + { + assert(false); + return 0; + } + + std::size_t io_context::poll() + { + assert(false); + return 0; + } + + std::size_t io_context::poll(boost::system::error_code&) + { + assert(false); + return 0; + } + + std::size_t io_context::poll_one() + { + assert(false); + return 0; + } + + std::size_t io_context::poll_one(boost::system::error_code&) + { + assert(false); + return 0; + } + + // private interface + + void io_context::add_timer(high_resolution_timer* t) + { + m_sim.add_timer(t); + } + + void io_context::remove_timer(high_resolution_timer* t) + { + m_sim.remove_timer(t); + } + + boost::asio::io_context& io_context::get_internal_service() + { return m_sim.get_internal_service(); } + + ip::tcp::endpoint io_context::bind_socket(ip::tcp::socket* socket + , ip::tcp::endpoint ep, boost::system::error_code& ec) + { + assert(!m_ips.empty() && "you cannot use an internal io_context (one without an IP address) for creating and binding sockets"); + if (ep.address() == ip::address_v4::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v4(); } ); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::tcp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (ep.address() == ip::address_v6::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v6(); } ); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::tcp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (std::count(m_ips.begin(), m_ips.end(), ep.address()) == 0) + { + // you can only bind to the IP assigned to this node. + // TODO: support loopback + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::tcp::endpoint(); + } + + return m_sim.bind_socket(socket, ep, ec); + } + + void io_context::unbind_socket(ip::tcp::socket* socket + , const ip::tcp::endpoint& ep) + { + m_sim.unbind_socket(socket, ep); + } + + void io_context::rebind_socket(asio::ip::tcp::socket* prev, asio::ip::tcp::socket* s, asio::ip::tcp::endpoint ep) + { + m_sim.rebind_socket(prev, s, ep); + } + + ip::udp::endpoint io_context::bind_udp_socket(ip::udp::socket* socket + , ip::udp::endpoint ep, boost::system::error_code& ec) + { + assert(!m_ips.empty() && "you cannot use an internal io_context (one without an IP address) for creating and binding sockets"); + if (ep.address() == ip::address_v4::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v4(); }); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::udp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (ep.address() == ip::address_v6::any()) + { + auto it = std::find_if(m_ips.begin(), m_ips.end() + , [](ip::address const& ip) { return ip.is_v6(); }); + if (it == m_ips.end()) + { + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::udp::endpoint(); + } + // TODO: pick the first local endpoint for now. In the future we may + // want have a bias toward + ep.address(*it); + } + else if (std::count(m_ips.begin(), m_ips.end(), ep.address()) == 0) + { + // you can only bind to the IP assigned to this node. + // TODO: support loopback + ec.assign(boost::system::errc::address_not_available + , boost::system::generic_category()); + return ip::udp::endpoint(); + } + + return m_sim.bind_udp_socket(socket, ep, ec); + } + + void io_context::unbind_udp_socket(ip::udp::socket* socket + , const ip::udp::endpoint& ep) + { + m_sim.unbind_udp_socket(socket, ep); + } + + void io_context::rebind_udp_socket(asio::ip::udp::socket* socket, asio::ip::udp::endpoint ep) + { + m_sim.rebind_udp_socket(socket, ep); + } + + std::shared_ptr io_context::internal_connect(ip::tcp::socket* s + , ip::tcp::endpoint const& target, boost::system::error_code& ec) + { + return m_sim.internal_connect(s, target, ec); + } + + route io_context::find_udp_socket(asio::ip::udp::socket const& socket + , ip::udp::endpoint const& ep) + { + return m_sim.find_udp_socket(socket, ep); + } + +} // asio +} // sim + diff --git a/simulation/libsimulator/src/nat.cpp b/simulation/libsimulator/src/nat.cpp new file mode 100644 index 0000000..de27f48 --- /dev/null +++ b/simulation/libsimulator/src/nat.cpp @@ -0,0 +1,47 @@ +/* + +Copyright (c) 2018, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/nat.hpp" +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include + +namespace sim { + + nat::nat(asio::ip::address external_addr) : m_external_addr(external_addr) {} + + void nat::incoming_packet(aux::packet p) + { + // unconditionally replacing the "from" address is fine because of our + // simplified network model where the two paths of a connection are set up + // independently, and we can set up the nat hop only on the outgoing path + p.from.address(m_external_addr); + if (p.channel) { + p.channel->visible_ep[0].address(m_external_addr); + } + forward_packet(std::move(p)); + } + + // used for visualization + std::string nat::label() const + { + return std::string("NAT [") + m_external_addr.to_string() + "]"; + } + +} // sim + diff --git a/simulation/libsimulator/src/pcap.cpp b/simulation/libsimulator/src/pcap.cpp new file mode 100644 index 0000000..d77aa4e --- /dev/null +++ b/simulation/libsimulator/src/pcap.cpp @@ -0,0 +1,204 @@ +/* + +Copyright (c) 2017, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/pcap.hpp" +#include "simulator/chrono.hpp" +#include "simulator/packet.hpp" + +using sim::chrono::duration_cast; +using sim::chrono::seconds; + +using sim::asio::ip::address_v4; +using sim::asio::ip::tcp; +using sim::asio::ip::udp; + +namespace sim { namespace aux { + +namespace { + + template ::value>::type> + void write(std::fstream& o, T const& value) + { o.write(reinterpret_cast(&value), sizeof(value)); } + + // documented here: + // http://www.tcpdump.org/linktypes.html + std::uint32_t const LINKTYPE_RAW = 101; + + struct ip_header + { + std::uint8_t version_len; + std::uint8_t dscp; + std::uint16_t length; + std::uint16_t identification; + std::uint16_t fragment; + std::uint8_t ttl; + std::uint8_t protocol; + std::uint16_t checksum; + std::uint32_t source_ip; + std::uint32_t destination_ip; + }; + + struct udp_header + { + std::uint16_t src_port; + std::uint16_t dst_port; + std::uint16_t length; + std::uint16_t checksum; + }; + + struct tcp_header + { + std::uint16_t src_port; + std::uint16_t dst_port; + std::uint32_t seq_nr; + std::uint32_t ack_nr; + std::uint8_t offset; + std::uint8_t flags; + std::uint16_t window_size; + std::uint16_t checksum; + std::uint16_t urgent; + }; +} + + pcap::pcap(char const* filename) + : m_file(filename, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary) + { + // file format documented here: + // https://wiki.wireshark.org/Development/LibpcapFileFormat + // write pcap file header + write(m_file, std::uint32_t(0xa1b2c3d4)); // magic number + write(m_file, std::uint16_t(2)); // versopm major + write(m_file, std::uint16_t(4)); // versopm minor + write(m_file, std::int32_t(0)); // thiszone + write(m_file, std::uint32_t(0)); // sigfigs + write(m_file, std::uint32_t(0xffff)); // snaplen + write(m_file, LINKTYPE_RAW); // network + } + + void write_ip_header(std::fstream& file, int const size, int const protocol + , address_v4 const source, address_v4 const dest) + { + ip_header const header = { + (4 << 4) | 5, // version_len + 0, // dscp + htons(std::uint16_t(size)), // length + 0, // identification + 0, // fragment + 200, // ttl + std::uint8_t(protocol), // protocol + 0, // checksum + htonl(std::uint32_t(source.to_uint())), // source ip + htonl(std::uint32_t(dest.to_uint())) // destination ip + }; + + write(file, header); + } + + void write_udp_header(std::fstream& file, int const size, int const src_port + , int const dst_port) + { + assert(src_port != 0); + assert(dst_port != 0); + udp_header const header = { + htons(std::uint16_t(src_port)), // source port + htons(std::uint16_t(dst_port)), // destination port + htons(std::uint16_t(sizeof(udp_header) + size)), // length + 0 // checksum + }; + + write(file, header); + } + + void write_tcp_header(std::fstream& file, int const src_port + , int const dst_port, std::uint32_t const seq_nr) + { + assert(src_port != 0); + assert(dst_port != 0); + tcp_header const header = { + htons(std::uint16_t(src_port)), // source port + htons(std::uint16_t(dst_port)), // destination port + htonl(seq_nr), // sequence number + std::uint32_t(0), // acknowledgement number + std::uint8_t(5 << 4), // header size (data offset) + std::uint8_t(0), // flags + htons(std::uint16_t(65535)), // window size + std::uint16_t(0), // checksum + std::uint16_t(0) // urgent offset + }; + + write(file, header); + } + + void pcap::log_tcp(packet const& p, tcp::endpoint const src + , tcp::endpoint const dst) + { + // synthesize IP/TCP header and write packet + auto const now = chrono::high_resolution_clock::now(); + + // just an arbitrary posix time used as starting point + std::uint32_t const sim_start_time = 441794304; + std::uint32_t const secs = static_cast(duration_cast(now.time_since_epoch()).count()); + std::uint32_t const usecs = static_cast(duration_cast(now.time_since_epoch() - seconds(secs)).count()); + + std::uint32_t const packet_size = static_cast(sizeof(ip_header) + sizeof(tcp_header) + p.buffer.size()); + + write(m_file, sim_start_time + secs); + write(m_file, usecs); + write(m_file, packet_size); + write(m_file, packet_size); + + // 6 is the protocol number for TCP + write_ip_header(m_file, packet_size, 6 + , src.address().to_v4(), dst.address().to_v4()); + + // TODO: if the packet has error set with asio::error::eof + // set the FIN flag + write_tcp_header(m_file, p.from.port(), dst.port(), p.byte_counter); + + m_file.write(reinterpret_cast(p.buffer.data()), p.buffer.size()); + } + + void pcap::log_udp(packet const& p, udp::endpoint const src + , udp::endpoint const dst) + { + // synthesize IP/UDP header and write packet + auto const now = chrono::high_resolution_clock::now(); + + // just an arbitrary posix time used as starting point + std::uint32_t const sim_start_time = 441794304; + std::uint32_t const secs = static_cast(duration_cast(now.time_since_epoch()).count()); + std::uint32_t const usecs = static_cast(duration_cast(now.time_since_epoch() - seconds(secs)).count()); + + std::uint32_t const packet_size = static_cast(sizeof(ip_header) + sizeof(udp_header) + p.buffer.size()); + + write(m_file, sim_start_time + secs); + write(m_file, usecs); + write(m_file, packet_size); + write(m_file, packet_size); + + // 17 is the protocol number for UDP + write_ip_header(m_file, packet_size, 17 + , src.address().to_v4(), dst.address().to_v4()); + + write_udp_header(m_file, static_cast(p.buffer.size()), p.from.port(), static_cast(dst.port())); + + m_file.write(reinterpret_cast(p.buffer.data()), p.buffer.size()); + } + +}} + diff --git a/simulation/libsimulator/src/queue.cpp b/simulation/libsimulator/src/queue.cpp new file mode 100644 index 0000000..69811bc --- /dev/null +++ b/simulation/libsimulator/src/queue.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/queue.hpp" +#include "simulator/handler_allocator.hpp" +#include +#include // for printf + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim +{ + using namespace aux; + + queue::queue(asio::io_context& ios + , int bandwidth + , chrono::high_resolution_clock::duration propagation_delay + , int max_queue_size + , std::string name) + : m_max_queue_size(max_queue_size) + , m_forwarding_latency(propagation_delay) + , m_bandwidth(bandwidth) + , m_queue_size(0) + , m_node_name(name) + , m_forward_timer(ios) + , m_last_forward(chrono::high_resolution_clock::now()) + {} + + std::string queue::label() const + { + char ret[400]; + int p = std::snprintf(ret, sizeof(ret), "%s\n", m_node_name.c_str()); + + if (m_bandwidth != 0) + { + p += std::snprintf(ret + p, sizeof(ret) - p, "rate: %d kB/s\n" + , m_bandwidth / 1000); + } + + if (m_queue_size != 0) + { + p += std::snprintf(ret + p, sizeof(ret) - p, "queue: %d kB\n" + , m_queue_size / 1000); + } + + if (m_forwarding_latency.count() != 0) + { + p += std::snprintf(ret + p, sizeof(ret) - p, "latency: %d ms\n" + , int(chrono::duration_cast(m_forwarding_latency).count())); + } + + return ret; + } + + void queue::incoming_packet(aux::packet p) + { + const int packet_size = int(p.buffer.size() + p.overhead); + + // tail-drop + if (p.ok_to_drop() + && m_max_queue_size > 0 + && m_queue_size + packet_size > m_max_queue_size) + { + // if any hop on the network drops a packet, it has to return it to the + // sender. + auto drop_fun = std::move(p.drop_fun); + if (drop_fun) drop_fun(std::move(p)); + return; + } + + time_point const now = chrono::high_resolution_clock::now(); + + m_queue.emplace_back(now + m_forwarding_latency, std::move(p)); + m_queue_size += packet_size; + if (m_queue.size() > 1) return; + + begin_send_next_packet(); + } + + void queue::begin_send_next_packet() + { + time_point now = chrono::high_resolution_clock::now(); + + if (m_queue.front().ts > now) + { + m_forward_timer.expires_at(m_queue.front().ts); + m_forward_timer.async_wait(make_malloc(std::bind(&queue::begin_send_next_packet + , this))); + return; + } + + m_last_forward = now; + if (m_bandwidth == 0) + { + post(m_forward_timer.get_executor(), make_malloc(std::bind(&queue::next_packet_sent + , this))); + return; + } + const double nanoseconds_per_byte = 1000000000.0 + / double(m_bandwidth); + + aux::packet const& p = m_queue.front().pkt; + const int packet_size = int(p.buffer.size() + p.overhead); + + m_last_forward += chrono::duration_cast(chrono::nanoseconds( + boost::int64_t(nanoseconds_per_byte * packet_size))); + + m_forward_timer.expires_at(m_last_forward); + m_forward_timer.async_wait(make_malloc(std::bind(&queue::next_packet_sent + , this))); + } + + void queue::next_packet_sent() + { + aux::packet p = std::move(m_queue.front().pkt); + m_queue.erase(m_queue.begin()); + const int packet_size = int(p.buffer.size() + p.overhead); + m_queue_size -= packet_size; + + forward_packet(std::move(p)); + + if (m_queue.size()) + begin_send_next_packet(); + } +} + diff --git a/simulation/libsimulator/src/resolver.cpp b/simulation/libsimulator/src/resolver.cpp new file mode 100644 index 0000000..01bc1ea --- /dev/null +++ b/simulation/libsimulator/src/resolver.cpp @@ -0,0 +1,143 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/handler_allocator.hpp" +#include + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +using namespace std::placeholders; + +namespace sim { +namespace asio { +namespace ip { + + template + basic_resolver::basic_resolver(io_context& ios) + : m_ios(&ios) + , m_timer(ios) + {} + + template + basic_resolver::basic_resolver(basic_resolver&&) noexcept = default; + + template + basic_resolver& basic_resolver::operator=(basic_resolver&&) noexcept = default; + + template + void basic_resolver::async_resolve(std::string hostname, char const* service + , aux::function handler) + { + std::vector result; + boost::system::error_code ec; + + const chrono::high_resolution_clock::time_point start_time = + m_queue.empty() ? chrono::high_resolution_clock::now() : + m_queue.front().completion_time; + + assert(!m_ios->get_ips().empty() && "internal io service objects can only " + "be used for timers"); + + // if the hostname is an IP address, resolve it immediately + asio::ip::address addr = make_address_v4(hostname, ec); + if (ec) addr = make_address_v6(hostname, ec); + if (!ec) + { + const chrono::high_resolution_clock::time_point t = chrono::high_resolution_clock::now() + + chrono::microseconds(1); + results_type ips; + int const port = atoi(service); + assert(port >= 0 && port <= 0xffff); + ips.emplace_back( + typename Protocol::endpoint(addr, static_cast(port)) + , hostname, service); + result_t res{t, ec, std::move(ips), std::move(handler) }; + m_queue.insert(m_queue.begin(), std::move(res)); + m_timer.expires_at(m_queue.front().completion_time); + m_timer.async_wait(aux::make_malloc(std::bind(&basic_resolver::on_lookup, this, _1))); + return; + } + ec.clear(); + + const chrono::high_resolution_clock::time_point completion_time = + start_time + + m_ios->sim().config().hostname_lookup(m_ios->get_ips().front(), hostname + , result, ec); + + results_type ips; + + int const port = atoi(service); + assert(port >= 0 && port <= 0xffff); + + for (auto const& ip : result) + { + ips.emplace_back( + typename Protocol::endpoint(ip, static_cast(port)) + , hostname, service); + } + + result_t res{ completion_time, ec, std::move(ips), std::move(handler)}; + m_queue.emplace_back(std::move(res)); + + m_timer.expires_at(m_queue.front().completion_time); + m_timer.async_wait(aux::make_malloc(std::bind(&basic_resolver::on_lookup, this, _1))); + } + + template + void basic_resolver::on_lookup(boost::system::error_code const& ec) + { + if (ec == asio::error::operation_aborted) return; + + if (m_queue.empty()) return; + + typename queue_t::value_type v = std::move(m_queue.front()); + m_queue.erase(m_queue.begin()); + + // once the handler is called, it's possible the last reference keeping + // this object (basic_resolver) alive is released and we're deleted. Make + // sure to not touch any members after the handler in that case. + bool const empty = m_queue.empty(); + v.handler(v.err, std::move(v.ips)); + if (empty) return; + + m_timer.expires_at(m_queue.front().completion_time); + m_timer.async_wait(aux::make_malloc(std::bind(&basic_resolver::on_lookup, this, _1))); + } + + template + void basic_resolver::cancel() + { + queue_t q; + m_queue.swap(q); + for (auto& r : q) + { + r.err = asio::error::operation_aborted; + post(m_timer.get_executor(), aux::make_malloc(std::bind(std::move(r.handler) + , r.err, std::move(r.ips)))); + } + } + + // explicitly instantiate the functions + template struct basic_resolver; + template struct basic_resolver; +} +} +} + diff --git a/simulation/libsimulator/src/simulation.cpp b/simulation/libsimulator/src/simulation.cpp new file mode 100644 index 0000000..c945558 --- /dev/null +++ b/simulation/libsimulator/src/simulation.cpp @@ -0,0 +1,381 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/pcap.hpp" + +#include // for tie +#include // for make_shared +#include // for printf +#include +#include // for std::runtime_error + +using namespace sim::asio; + +namespace sim +{ + simulation::simulation(configuration& config) + : m_config(config) + , m_internal_ios(new asio::io_context(*this)) + , m_service(1) + { + sim::chrono::reset_clock(); + m_config.build(*this); + } + + simulation::~simulation() + { + m_config.clear(); + + assert(m_timer_queue.empty()); + } + + std::size_t simulation::run() try + { + std::size_t ret = 0; + std::size_t last_executed = 0; + + // Detect a runaway loop where events keep firing but simulated time + // never advances - typically a timer being repeatedly scheduled at the + // current time (a 100% CPU spin that would otherwise hang run() + // forever). Legitimate bursts of same-timestamp events are finite, so + // a large threshold reliably distinguishes them from an unbounded spin. + chrono::high_resolution_clock::time_point prev_time + = chrono::high_resolution_clock::now(); + std::int64_t stalled_iterations = 0; + constexpr std::int64_t max_stalled_iterations = 1000; + + do { + + m_service.restart(); + last_executed = m_service.poll(); + ret += last_executed; + + chrono::high_resolution_clock::time_point now + = chrono::high_resolution_clock::now(); + + std::lock_guard l(m_timer_queue_mutex); + if (!m_timer_queue.empty()) { + asio::high_resolution_timer* next_timer = *m_timer_queue.begin(); + chrono::high_resolution_clock::fast_forward(next_timer->expiry() - now); + + now = chrono::high_resolution_clock::now(); + + while (!m_timer_queue.empty() + && (*m_timer_queue.begin())->expiry() <= now) { + + next_timer = *m_timer_queue.begin(); + m_timer_queue.erase(m_timer_queue.begin()); + next_timer->fire(boost::system::error_code()); + ++last_executed; + ++ret; + } + } + + if (last_executed > 0) { + if (now == prev_time) { + if (++stalled_iterations > max_stalled_iterations) { + throw std::runtime_error("libsimulator: simulated time " + "has not advanced for too many consecutive iterations " + "while events keep firing. The simulation is stuck in a " + "busy loop, typically a timer repeatedly scheduled at " + "the current time (a 100% CPU spin)."); + } + } else { + stalled_iterations = 0; + prev_time = now; + } + } + +// std::fprintf(stderr, "run: last_executed: %d stopped: %d timer-queue: %d\n" +// , int(last_executed), m_stopped, int(m_timer_queue.size())); + } while (last_executed > 0 && !m_stopped); + +// std::fprintf(stderr, "exiting simulation::run(): last_executed: %d stopped: %d timer-queue: %d ret: %d\n" +// , int(last_executed), m_stopped, int(m_timer_queue.size()), int(ret)); + return ret; + } + catch (...) + { + // cancel all outstanding timers + // we make a copy, since cancelling the timers will mutate m_timer_queue + auto queue = m_timer_queue; + for (auto& t : queue) + t->cancel(); + + auto listen_sockets = m_listen_sockets; + for (auto& s : listen_sockets) + s.second->cancel(); + + auto udp_sockets = m_udp_sockets; + for (auto& s : udp_sockets) + s.second->cancel(); + m_stopped = true; + throw; + } + + void simulation::stop() { m_stopped = true; } + bool simulation::stopped() const { return m_stopped; } + void simulation::restart() { m_stopped = false; } + + void simulation::add_timer(asio::high_resolution_timer* t) + { + assert(!m_stopped); + if (t->expiry() == sim::chrono::high_resolution_clock::now()) + { + std::fprintf(stderr, "WARNING: timer scheduled for current time!\n"); + } + std::lock_guard l(m_timer_queue_mutex); + // make sure we get a deterministic ordering of timers with the same + // expiration time + auto it = std::upper_bound(m_timer_queue.begin(), m_timer_queue.end(), t, timer_compare()); + m_timer_queue.insert(it, t); + } + + void simulation::remove_timer(asio::high_resolution_timer* t) + { + std::lock_guard l(m_timer_queue_mutex); + if (m_timer_queue.empty()) return; + timer_queue_t::iterator begin; + timer_queue_t::iterator end; + std::tie(begin, end) = std::equal_range(m_timer_queue.begin(), m_timer_queue.end(), t, timer_compare()); + if (begin == end) return; + begin = std::find(begin, end, t); + if (begin == end) return; + m_timer_queue.erase(begin); + } + + void simulation::rebind_socket(ip::tcp::socket* prev, ip::tcp::socket* s, ip::tcp::endpoint ep) + { + auto i = m_listen_sockets.find(ep); + assert(i != m_listen_sockets.end()); + if (i->second != prev) return; + i->second = s; + } + + ip::tcp::endpoint simulation::bind_socket(ip::tcp::socket* socket + , ip::tcp::endpoint ep, boost::system::error_code& ec) + { + assert(ep.address() != boost::asio::ip::address()); + + if (ep.port() < 1024 && ep.port() > 0) + { + // emulate process not running as root + ec = boost::asio::error::access_denied; + return ip::tcp::endpoint(); + } + + if (ep.port() == 0) + { + // if the socket is being bound to port 0, it means the system picks a + // free port. We want to avoid re-using ports, because that may confuse + // wireshark when threading together the TCP streams. + ep.port(m_next_bind_port++); + if (m_next_bind_port > 65534) m_next_bind_port = 2000; + + listen_socket_iter_t i = m_listen_sockets.lower_bound(ep); + while (i != m_listen_sockets.end() && i->first == ep) + { + ep.port(ep.port() + 1); + if (ep.port() > 65530) + { + ec = boost::asio::error::address_in_use; + return ip::tcp::endpoint(); + } + i = m_listen_sockets.lower_bound(ep); + } + } + + listen_socket_iter_t i = m_listen_sockets.lower_bound(ep); + if (i != m_listen_sockets.end() && i->first == ep) + { + ec = boost::asio::error::address_in_use; + return ip::tcp::endpoint(); + } + + m_listen_sockets.insert(i, std::make_pair(ep, socket)); + ec.clear(); + return ep; + } + + void simulation::unbind_socket(ip::tcp::socket* socket + , const ip::tcp::endpoint& ep) + { + listen_socket_iter_t i = m_listen_sockets.find(ep); + if (i == m_listen_sockets.end() || i->second != socket) return; + m_listen_sockets.erase(i); + } + + void simulation::rebind_udp_socket(ip::udp::socket* socket, ip::udp::endpoint ep) + { + auto i = m_udp_sockets.find(ep); + assert(i != m_udp_sockets.end()); + i->second = socket; + } + + ip::udp::endpoint simulation::bind_udp_socket(ip::udp::socket* socket + , ip::udp::endpoint ep, boost::system::error_code& ec) + { + assert(ep.address() != boost::asio::ip::address()); + + if (ep.port() < 1024 && ep.port() > 0) + { + // emulate process not running as root + ec = boost::asio::error::access_denied; + return ip::udp::endpoint(); + } + + if (ep.port() == 0) + { + // if the socket is being bound to port 0, it means the system picks a + // free port. + + ep.port(m_next_bind_port++); + if (m_next_bind_port > 65534) m_next_bind_port = 2000; + udp_socket_iter_t i = m_udp_sockets.lower_bound(ep); + while (i != m_udp_sockets.end() && i->first == ep) + { + ep.port(ep.port() + 1); + if (ep.port() > 65530) + { + ec = boost::asio::error::address_in_use; + return ip::udp::endpoint(); + } + i = m_udp_sockets.lower_bound(ep); + } + } + + udp_socket_iter_t i = m_udp_sockets.lower_bound(ep); + if (i != m_udp_sockets.end() && i->first == ep) + { + ec = boost::asio::error::address_in_use; + return ip::udp::endpoint(); + } + + m_udp_sockets.insert(i, std::make_pair(ep, socket)); + ec.clear(); + return ep; + } + + void simulation::unbind_udp_socket(ip::udp::socket* socket + , const ip::udp::endpoint& ep) + { + udp_socket_iter_t i = m_udp_sockets.find(ep); + if (i == m_udp_sockets.end() || i->second != socket) return; + m_udp_sockets.erase(i); + } + + std::shared_ptr simulation::internal_connect( + asio::ip::tcp::socket* s + , ip::tcp::endpoint const& target, boost::system::error_code& ec) + { + // find remote socket + listen_sockets_t::iterator i = m_listen_sockets.find(target); + if (i == m_listen_sockets.end()) + { + ec = boost::system::error_code(error::connection_refused); + return std::shared_ptr(); + } + + // make sure it's a listening socket + ip::tcp::socket* remote = i->second; + if (!remote->internal_is_listening()) + { + ec = boost::system::error_code(error::connection_refused); + return std::shared_ptr(); + } + + // create a channel + std::shared_ptr c = std::make_shared(); + + asio::ip::tcp::endpoint from = s->local_bound_to(ec); + + route network_route = m_config.channel_route(from.address() + , target.address()); + c->hops[0] = remote->get_outgoing_route() + network_route + s->get_incoming_route(); + c->hops[1] = s->get_outgoing_route() + network_route + remote->get_incoming_route(); + + c->ep[0] = s->local_bound_to(ec); + c->ep[1] = remote->local_bound_to(ec); + + c->visible_ep[0] = s->local_bound_to(ec); + c->visible_ep[1] = remote->local_bound_to(ec); + + aux::packet p; + p.type = aux::packet::type_t::syn; + p.overhead = 28; + p.from = asio::ip::udp::endpoint(from.address(), from.port()); + p.channel = c; + if (ec) return std::shared_ptr(); + + p.hops = c->hops[1]; + + forward_packet(std::move(p)); + + return c; + } + + route simulation::find_udp_socket(asio::ip::udp::socket const& socket + , ip::udp::endpoint const& ep) + { + udp_socket_iter_t i = m_udp_sockets.find(ep); + if (i == m_udp_sockets.end()) + return route(); + + ip::udp::endpoint src = socket.local_bound_to(); + route network_route = m_config.channel_route(src.address(), ep.address()); + + // ask the socket for its incoming route + network_route.append(i->second->get_incoming_route()); + + return network_route; + } + + void simulation::add_io_service(asio::io_context* ios) + { + bool added = m_nodes.insert(ios).second; + (void)added; + assert(added); + } + + void simulation::remove_io_service(asio::io_context* ios) + { + auto it = m_nodes.find(ios); + assert(it != m_nodes.end()); + m_nodes.erase(it); + } + + std::vector simulation::get_all_io_services() const + { + std::vector ret; + ret.reserve(m_nodes.size()); + std::remove_copy_if( + m_nodes.begin(), m_nodes.end(), std::back_inserter(ret) + , [](io_context* ios) { return ios->get_ips().empty(); }); + return ret; + } + + void simulation::log_pcap(char const* filename) + { + std::printf("saving packet capture to: \"%s\"\n", filename); + m_pcap = std::unique_ptr(new aux::pcap(filename)); + } + +} + diff --git a/simulation/libsimulator/src/simulator.cpp b/simulation/libsimulator/src/simulator.cpp new file mode 100644 index 0000000..0d004ad --- /dev/null +++ b/simulation/libsimulator/src/simulator.cpp @@ -0,0 +1,262 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" + +#include +#include +#include +#include // for printf + +#include "simulator/push_warnings.hpp" +#include +#include "simulator/pop_warnings.hpp" + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim { +namespace asio { +namespace ip { + + default_config default_cfg; + +} // ip +} // asio + +void forward_packet(aux::packet p) +{ + std::shared_ptr next_hop = p.hops.pop_front(); + if (!next_hop) + { + std::fprintf(stderr, "packet lost\n"); + return; + } + next_hop->incoming_packet(std::move(p)); +} + +namespace +{ + // this is a dummy sink for endpoints, wrapping an io_context + struct endpoint : sink + { + endpoint(asio::io_context& ioc) + : m_ioc(ioc) + {} + + virtual void incoming_packet(aux::packet /* p */) override final { assert(false); } + + virtual std::string label() const override final + { + std::string ret; + for (auto const& ip : m_ioc.get_ips()) + { + ret += ip.to_string(); + ret += " "; + } + return ret; + } + + virtual std::string attributes() const override final + { + return "shape=ellipse"; + } + + private: + asio::io_context& m_ioc; + }; +} + +namespace +{ + std::string escape_label(std::string n) + { + std::string ret; + for (auto c : n) + { + if (c == '\n') + { + ret += "\\n"; + continue; + } + if (c == '\"') + { + ret += "\\\""; + continue; + } + ret += c; + } + return ret; + } +} + +void dump_network_graph(simulation const& s, const std::string& filename) +{ + // all edges (directed). + std::set, std::shared_ptr>> edges; + + // all network nodes + std::unordered_set> nodes; + + // local nodes (subgrapgs) + std::vector>> local_nodes; + + const std::vector io_services = s.get_all_io_services(); + + for (auto ioc : io_services) + { + std::shared_ptr ep = std::make_shared(*ioc); + local_nodes.push_back(std::unordered_set>()); + local_nodes.back().insert(ep); + + for (auto const& ip : ioc->get_ips()) + { + route in = ioc->get_incoming_route(ip); + route out = ioc->get_outgoing_route(ip); + + // this is the outgoing node for this endpoint. This is + // how it connects to the network. + const std::shared_ptr egress = out.empty() ? ep : out.last(); + + // first add both the incoming and outgoing chains + std::shared_ptr prev; + while (!in.empty()) + { + auto node = in.pop_front(); + local_nodes.back().insert(node); + if (prev) edges.insert({prev, node}); + prev = node; + } + if (prev) edges.insert({prev, ep}); + + prev = ep; + while (!out.empty()) + { + auto node = out.pop_front(); + local_nodes.back().insert(node); + edges.insert({prev, node}); + prev = node; + } + + // then connect the endpoint of those chains to the rest of the network. + // Since the network may be arbitrarily complex, we actually have to + // completely iterate over all other endpoints + + for (auto ios2 : io_services) + { + for (auto const& ip2 : ios2->get_ips()) + { + route network = s.config().channel_route( + ip, ip2); + + std::shared_ptr last = ios2->get_incoming_route(ip2).next_hop(); + + prev = egress; + while (!network.empty()) + { + auto node = network.pop_front(); + nodes.insert(node); + edges.insert({prev, node}); + prev = node; + } + edges.insert({prev, last}); + } + } + } + } + + // by now, the nodes and edges should represent the complete graph. Render it + // into dot. + + FILE* f = fopen(filename.c_str(), "w+"); + + std::fprintf(f, "digraph network {\n" + "concentrate=true;\n" + "overlap=scale;\n" + "splines=true;\n"); + + std::fprintf(f, "\n// nodes\n\n"); + + for (const auto& n : nodes) + { + std::string attributes = n->attributes(); + std::fprintf(f, " \"%p\" [label=\"%s\",style=\"filled\",color=\"red\"%s%s];\n" + , static_cast(n.get()) + , escape_label(n->label()).c_str() + , attributes.empty() ? "" : ", " + , attributes.c_str()); + } + + std::fprintf(f, "\n// local networks\n\n"); + + int idx = 0; + for (auto ln : local_nodes) + { + std::fprintf(f, "subgraph cluster_%d {\n", idx++); + + for (const auto& n : ln) + { + std::string attributes = n->attributes(); + std::fprintf(f, " \"%p\" [label=\"%s\",style=\"filled\",color=\"green\"%s%s];\n" + , static_cast(n.get()) + , escape_label(n->label()).c_str() + , attributes.empty() ? "" : ", " + , attributes.c_str()); + } + + std::fprintf(f, "}\n"); + } + + std::fprintf(f, "\n// edges\n\n"); + + while (!edges.empty()) + { + auto edge = *edges.begin(); + edges.erase(edges.begin()); + + std::fprintf(f, "\"%p\" -> \"%p\"\n" + , static_cast(edge.first.get()) + , static_cast(edge.second.get())); + } + + std::fprintf(f, "}\n"); + fclose(f); +} + +namespace aux { + + int channel::remote_idx(const asio::ip::tcp::endpoint& self) const + { + if (ep[0] == self) return 1; + if (ep[1] == self) return 0; + assert(false && "invalid socket"); + return -1; + } + + int channel::self_idx(const asio::ip::tcp::endpoint& self) const + { + if (ep[0] == self) return 0; + if (ep[1] == self) return 1; + assert(false && "invalid socket"); + return -1; + } + +} // aux +} // sim + diff --git a/simulation/libsimulator/src/sink_forwarder.cpp b/simulation/libsimulator/src/sink_forwarder.cpp new file mode 100644 index 0000000..e98dc2f --- /dev/null +++ b/simulation/libsimulator/src/sink_forwarder.cpp @@ -0,0 +1,45 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/sink_forwarder.hpp" +#include "simulator/packet.hpp" + +namespace sim { namespace aux { + + sink_forwarder::sink_forwarder(sink* dst) + : m_dst(dst) + {} + + void sink_forwarder::incoming_packet(packet p) + { + if (m_dst == nullptr) return; + m_dst->incoming_packet(std::move(p)); + } + + std::string sink_forwarder::label() const + { + return m_dst ? m_dst->label() : ""; + } + + void sink_forwarder::reset(sink* s) + { + m_dst = s; + } + +}} + diff --git a/simulation/libsimulator/src/socks_server.cpp b/simulation/libsimulator/src/socks_server.cpp new file mode 100644 index 0000000..e0083ad --- /dev/null +++ b/simulation/libsimulator/src/socks_server.cpp @@ -0,0 +1,1033 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/socks_server.hpp" + +#include +#include +#include +#include // for printf + +using namespace sim::asio; +using namespace sim::asio::ip; + +using boost::system::error_code; + +namespace sim +{ + using namespace aux; + + socks_server::socks_server(io_context& ios, unsigned short listen_port, int version + , std::uint32_t const flags) + : m_ios(ios) + , m_listen_socket(ios) + , m_conn(std::make_shared(m_ios, version, m_cmd_counts, flags, m_bind_port)) + , m_version(version) + , m_flags(flags) + { + m_cmd_counts.fill(0); + address local_ip = ios.get_ips().front(); + if (local_ip.is_v4()) + { + m_listen_socket.open(tcp::v4()); + m_listen_socket.bind(tcp::endpoint(address_v4::any(), listen_port)); + } + else + { + m_listen_socket.open(tcp::v6()); + m_listen_socket.bind(tcp::endpoint(address_v6::any(), listen_port)); + } + m_listen_socket.listen(); + + m_listen_socket.async_accept(m_conn->socket(), m_ep + , std::bind(&socks_server::on_accept, this, std::placeholders::_1)); + } + + void socks_server::on_accept(error_code const& ec) + { + if (ec == asio::error::operation_aborted) + return; + + if (ec) + { + std::printf("socks_server::on_accept: (%d) %s\n" + , ec.value(), ec.message().c_str()); + return; + } + + std::printf("socks_server accepted connection from: %s : %d\n", + m_ep.address().to_string().c_str(), m_ep.port()); + + m_conn->start(); + + // create a new connection to accept into + m_conn = std::make_shared(m_ios, m_version, m_cmd_counts, m_flags, m_bind_port); + + // now we can accept another connection + m_listen_socket.async_accept(m_conn->socket(), m_ep + , std::bind(&socks_server::on_accept, this, std::placeholders::_1)); + } + + void socks_server::stop() + { + m_close = true; + m_listen_socket.close(); + } + + socks_connection::socks_connection(asio::io_context& ios + , int version, std::array& cmd_counts, std::uint32_t const flags, int& bind_port) + : m_bind_port(bind_port) + , m_ios(ios) + , m_udp_resolver(ios) + , m_resolver(m_ios) + , m_client_connection(ios) + , m_server_connection(m_ios) + , m_bind_socket(m_ios) + , m_udp_associate(m_ios) + , m_num_out_bytes(0) + , m_num_in_bytes(0) + , m_version(version) + , m_command(0) + , m_cmd_counts(cmd_counts) + , m_flags(flags) + { + } + + void socks_connection::start() + { + if (m_version == 4) + { + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], 9) + , std::bind(&socks_connection::on_request1, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } else { + // read protocol version and number of auth-methods + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], 2) + , std::bind(&socks_connection::on_handshake1, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + } + + void socks_connection::on_handshake1(error_code const& ec, size_t bytes_transferred) + { + if (ec || bytes_transferred != 2) + { + std::printf("socks_connection::on_handshake1: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + if (m_out_buffer[0] != 4 && m_out_buffer[0] != 5) + { + std::printf("socks_connection::on_handshake1: unexpected socks protocol version: %d" + , int(m_out_buffer[0])); + close_connection(); + return; + } + + int num_methods = unsigned(m_out_buffer[1]); + + // read list of auth-methods + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], + num_methods) + , std::bind(&socks_connection::on_handshake2, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_handshake2(error_code const& ec, size_t bytes_transferred) + { + if (ec) + { + std::printf("socks_connection::on_handshake2: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + if (std::count(m_out_buffer, m_out_buffer + bytes_transferred, 0) == 0) + { + std::printf("socks_connection: could not find auth-method 0 (no-auth) in socks handshake\n"); + close_connection(); + return; + } + + m_in_buffer[0] = 5; // socks version + m_in_buffer[1] = 0; // auth-method (no-auth) + + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], 2) + , std::bind(&socks_connection::on_handshake3, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_handshake3(error_code const& ec, size_t bytes_transferred) + { + if (ec || bytes_transferred != 2) + { + std::printf("socks_connection::on_handshake3: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[0], 10) + , std::bind(&socks_connection::on_request1, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_request1(error_code const& ec, size_t bytes_transferred) + { + size_t const expected = m_version == 4 ? 9 : 10; + if (ec || bytes_transferred != expected) + { + std::printf("socks_connection::on_request1: (%d) %s\n" + , ec.value(), ec.message().c_str()); + close_connection(); + return; + } + +// +----+-----+-------+------+----------+----------+ +// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + int const version = m_out_buffer[0]; + int const command = m_out_buffer[1]; + m_command = command; + ++m_cmd_counts[command - 1]; + + if (version != m_version) + { + std::printf("socks_connection::on_request1: unexpected socks protocol version: %d expected: %d\n" + , int(m_out_buffer[0]), m_version); + close_connection(); + return; + } + + if (m_version == 4) + { + if (command != 1 && command != 2) + { + std::printf("socks_connection::on_request1: unexpected socks command: %d\n" + , command); + close_connection(); + return; + } + + std::uint16_t port = m_out_buffer[2] & 0xff; + port <<= 8; + port |= m_out_buffer[3] & 0xff; + + std::uint32_t addr = m_out_buffer[4] & 0xff; + addr <<= 8; + addr |= m_out_buffer[5] & 0xff; + addr <<= 8; + addr |= m_out_buffer[6] & 0xff; + addr <<= 8; + addr |= m_out_buffer[7] & 0xff; + + if (m_out_buffer[8] != 0) + { + // in this case, we would have to read one byte at a time until we + // get to the null terminator. + std::printf("socks_connection::on_request1: username in SOCKS4 mode not supported\n"); + close_connection(); + return; + } + asio::ip::tcp::endpoint target(asio::ip::address_v4(addr), port); + if (command == 1) + { + open_forward_connection(target); + } + else if (command == 2) + { + bind_connection(target); + } + + return; + } + + if (command != 1 && command != 2 && command != 3) + { + std::printf("socks_connection::on_request1: unexpected command: %d\n" + , command); + close_connection(); + return; + } + + if (m_out_buffer[2] != 0) + { + std::printf("socks_connection::on_request1: reserved byte is non-zero: %d\n" + , int(m_out_buffer[2])); + close_connection(); + return; + } + + int atyp = unsigned(m_out_buffer[3]); + + if (atyp != 1 && atyp != 3 && atyp != 4) + { + std::printf("socks_connection::on_request1: unexpected address type in SOCKS request: %d\n" + , atyp); + close_connection(); + return; + } + + std::printf("socks_connection: received %s request address type: %d\n" + , command == 1 ? "CONNECT" + : command == 2 ? "BIND" + : "UDP_ASSOCIATE", atyp); + + switch (atyp) + { + case 1: { // IPv4 address (we have the whole request already) + +// +----+-----+-------+------+----------+----------+ +// |VER | CMD | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | 4 | 2 | +// +----+-----+-------+------+----------+----------+ + + std::uint32_t addr = m_out_buffer[4] & 0xff; + addr <<= 8; + addr |= m_out_buffer[5] & 0xff; + addr <<= 8; + addr |= m_out_buffer[6] & 0xff; + addr <<= 8; + addr |= m_out_buffer[7] & 0xff; + + std::uint16_t port = m_out_buffer[8] & 0xff; + port <<= 8; + port |= m_out_buffer[9] & 0xff; + + asio::ip::tcp::endpoint target(asio::ip::address_v4(addr), port); + if (command == 1) + { + open_forward_connection(target); + } + else if (command == 2) + { + bind_connection(target); + } + else if (command == 3) + { + if (target.address() == address()) + { + target.address(m_client_connection.remote_endpoint().address()); + } + udp_associate(target); + } + + break; + } + case 3: { // domain name + +// +----+-----+-------+------+-----+----------+----------+ +// |VER | CMD | RSV | ATYP | LEN | BND.ADDR | BND.PORT | +// +----+-----+-------+------+-----+----------+----------+ +// | 1 | 1 | X'00' | 1 | 1 | Variable | 2 | +// +----+-----+-------+------+-----+----------+----------+ + + if (command == 2) + { + std::printf("ERROR: cannot BIND to hostname address (only IPv4 or IPv6 addresses)\n"); + close_connection(); + return; + } + + const int len = std::uint8_t(m_out_buffer[4]); + // we already read an address of length 4, assuming it was an IPv4 + // address. Now, with a domain name, one of those bytes was the + // length-prefix, but we still read 3 bytes already. + const int additional_bytes = len - 3; + asio::async_read(m_client_connection, asio::buffer(&m_out_buffer[10], additional_bytes) + , std::bind(&socks_connection::on_request_domain_name + , shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + break; + } + case 4: // IPv6 address + +// +----+-----+-------+------+----------+----------+ +// |VER | CMD | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | 16 | 2 | +// +----+-----+-------+------+----------+----------+ + + std::printf("ERROR: unsupported address type %d\n", atyp); + close_connection(); + } + } + + void socks_connection::on_request_domain_name(error_code const& ec, size_t bytes_transferred) + { + if (ec) + { + std::printf("socks_connection::on_request_domain_name(%s): (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + int const buffer_size = int(10 + bytes_transferred); + + std::uint16_t port = m_out_buffer[buffer_size - 2] & 0xff; + port <<= 8; + port |= m_out_buffer[buffer_size - 1] & 0xff; + + std::string hostname(&m_out_buffer[5], std::uint8_t(m_out_buffer[4])); + std::printf("socks_connection::on_request_domain_name(%s): hostname: %s port: %d\n" + , command(), hostname.c_str(), port); + + char port_str[10]; + std::snprintf(port_str, sizeof(port_str), "%d", port); + m_resolver.async_resolve(hostname, port_str + , std::bind(&socks_connection::on_request_domain_lookup + , shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_request_domain_lookup(boost::system::error_code const& ec + , asio::ip::tcp::resolver::results_type const ips) + { + if (ec || ips.empty()) + { + if (ec) + { + std::printf("socks_connection::on_request_domain_lookup(%s): (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + } + else + { + std::printf("socks_connection::on_request_domain_lookup(%s): empty response\n" + , command()); + } + +// +----+-----+-------+------+----------+----------+ +// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + m_in_buffer[0] = char(m_version); // version + m_in_buffer[1] = 4; // response (host unreachable) + m_in_buffer[2] = 0; // reserved + m_in_buffer[3] = 1; // IPv4 + memset(&m_in_buffer[4], 0, 4); + m_in_buffer[8] = 0; // port + m_in_buffer[9] = 0; + + auto self = shared_from_this(); + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], 10) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + std::printf("socks_connection::on_request_domain_lookup(%s): connecting to: %s port: %d\n" + , command() + , ips.front().endpoint().address().to_string().c_str() + , ips.front().endpoint().port()); + open_forward_connection(ips.front().endpoint()); + } + + void socks_connection::open_forward_connection(const asio::ip::tcp::endpoint& target) + { + std::printf("socks_connection::open_forward_connection(%s): connecting to %s port %d\n" + , command(), target.address().to_string().c_str(), target.port()); + + m_server_connection.open(target.protocol()); + m_server_connection.async_connect(target + , std::bind(&socks_connection::on_connected, shared_from_this() + , std::placeholders::_1)); + } + + void socks_connection::bind_connection(const asio::ip::tcp::endpoint& target) + { + std::printf("socks_connection::bind_connection(%s): binding to %s port %d\n" + , command(), target.address().to_string().c_str(), target.port()); + + error_code ec; + m_bind_socket.open(target.protocol(), ec); + if (ec) + { + std::printf("ERROR: open bind socket failed: (%d) %s\n", ec.value() + , ec.message().c_str()); + } + else + { + m_bind_socket.bind(target, ec); + } + + int const response = ec + ? (m_version == 4 ? 91 : 1) + : (m_version == 4 ? 90 : 0); + tcp::endpoint ep = m_bind_socket.local_endpoint(); + int const len = format_response(ep.address(), ep.port(), response); + + if (ec) + { + std::printf("ERROR: binding socket to %s %d failed: (%d) %s\n" + , target.address().to_string().c_str() + , target.port() + , ec.value() + , ec.message().c_str()); + + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + // send response + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , std::bind(&socks_connection::start_accept, shared_from_this(), std::placeholders::_1)); + } + + void socks_connection::udp_associate(const asio::ip::tcp::endpoint& target) + { + std::printf("socks_connection::udp_associate(%s): %s:%d\n" + , command(), target.address().to_string().c_str(), target.port()); + + m_udp_associate_ep.address(target.address()); + m_udp_associate_ep.port(target.port()); + + error_code ec; + m_udp_associate.open(m_udp_associate_ep.protocol(), ec); + if (ec) + { + std::printf("ERROR: open UDP associate socket failed: (%d) %s\n", ec.value() + , ec.message().c_str()); + } + else + { + m_udp_associate.bind(udp::endpoint(address_v4(), std::uint16_t(m_bind_port++)), ec); + if (ec) + { + std::printf("ERROR: binding socket failed: (%d) %s\n" + , ec.value(), ec.message().c_str()); + } + else + { + m_udp_associate.non_blocking(true); + m_udp_associate.async_receive_from(boost::asio::buffer(m_udp_buffer) + , m_udp_from, 0, std::bind(&socks_connection::on_read_udp, this, std::placeholders::_1, std::placeholders::_2)); + } + } + + int const response = ec ? 1 : 0; + udp::endpoint ep = m_udp_associate.local_bound_to(); + int const len = (m_flags & udp_associate_respond_empty_hostname) + ? format_hostname_response("foobar", ep.port(), response) + : format_response(ep.address(), ep.port(), response); + + if (ec) + { + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + // send response + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], len) + , std::bind(&socks_connection::wait_for_eof, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::wait_for_eof(boost::system::error_code const& ec, std::size_t) + { + if (ec) + { + std::printf("socks_connection::wait_for_eof: %s\n", ec.message().c_str()); + m_udp_associate.close(); + m_udp_associate_ep = udp::endpoint(); + m_client_connection.close(); + return; + } + + if (m_flags & socks_flag::disconnect_udp_associate) + { + std::printf("socks_connection::wait_for_eof: closing connection prematurely\n"); + m_client_connection.close(); + return; + } + + m_client_connection.async_read_some( + asio::buffer(m_out_buffer) + , std::bind(&socks_connection::wait_for_eof, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_read_udp(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + std::printf("socks_connection::on_read_udp\n"); + if (ec) + { + std::printf("socks_connection::on_read_udp: %s\n", ec.message().c_str()); + return; + } + + // if the client didn't specify an IP and port it would send packets from, + // we assumed the same IP as the TCP connection and assume the port is the + // same as the first UDP packet from that host + if (m_udp_associate_ep.port() == 0 + && m_udp_from.address() == m_udp_associate_ep.address()) + { + m_udp_associate_ep.port(m_udp_from.port()); + } + + if (m_udp_from == m_udp_associate_ep) + { + // read UDP ASSICOATE header and forward outgoing packet + // +----+------+------+----------+----------+----------+ + // |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | + // +----+------+------+----------+----------+----------+ + // | 2 | 1 | 1 | Variable | 2 | Variable | + // +----+------+------+----------+----------+----------+ + + char const* buf = m_udp_buffer.data(); + if (buf[2] != 0) std::printf("fragment != 0, not supported\n"); + + int const atyp = buf[3]; + if (atyp == 3) + { + // hostname + int const len = buf[4]; + + buf += 5; + bytes_transferred -= 5; + std::string const hostname(buf, len); + buf += len; + bytes_transferred -= len; + + std::uint16_t port = buf[0] & 0xff; + port <<= 8; + port |= buf[1] & 0xff; + buf += 2; + bytes_transferred -= 2; + + auto it = m_name_mapping.right.find(hostname); + if (it != m_name_mapping.right.end()) + { + error_code err; + m_udp_associate.send_to(boost::asio::buffer(buf, bytes_transferred) + , udp::endpoint(it->second, port), 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + return; + } + + std::vector forward_buffer(buf, buf + bytes_transferred); + + m_udp_resolver.async_resolve(hostname.c_str(), std::to_string(port).c_str() + , [buf=std::move(forward_buffer), hostname, this] + (error_code const& ec, asio::ip::udp::resolver::results_type ips) + { + if (ec) + { + std::printf("resolve failed: %s\n", ec.message().c_str()); + return; + } + + for (auto const& ip : ips) + { + auto const target = ip.endpoint(); + error_code err; + m_udp_associate.send_to(boost::asio::buffer(buf) + , target, 0, err); + if (!err) + { + m_name_mapping.insert({target.address(), hostname}); + break; + } + std::printf("send_to failed: %s\n", err.message().c_str()); + } + }); + } + else if (atyp == 1) + { + // IPv4 + std::uint32_t addr = buf[4] & 0xff; + addr <<= 8; + addr |= buf[5] & 0xff; + addr <<= 8; + addr |= buf[6] & 0xff; + addr <<= 8; + addr |= buf[7] & 0xff; + + std::uint16_t port = buf[8] & 0xff; + port <<= 8; + port |= buf[9] & 0xff; + + buf += 10; + bytes_transferred -= 10; + + asio::ip::udp::endpoint const target(address_v4(addr), port); + + error_code err; + m_udp_associate.send_to(boost::asio::buffer(buf, bytes_transferred), target, 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + } + else + { + std::printf("only supports IPv4 and hostname. ATYP: %d\n", atyp); + } + } + else + { + std::uint16_t const from_port = m_udp_from.port(); + + auto it = m_name_mapping.left.find(m_udp_from.address()); + if (it != m_name_mapping.left.end()) + { + std::vector header(7 + it->second.size()); + header[0] = 0; // RSV + header[1] = 0; + header[2] = 0; // fragment + header[3] = 3; // ATYP + header[4] = static_cast(it->second.size()); + int idx = 5; + std::copy(it->second.begin(), it->second.end(), header.data() + idx); + idx += static_cast(it->second.size()); + header[idx] = (from_port >> 8) & 0xff; + header[idx + 1] = from_port & 0xff; + + std::array vec{{ + {header.data(), header.size()}, + {m_udp_buffer.data(), bytes_transferred}}}; + + error_code err; + m_udp_associate.send_to(vec, m_udp_associate_ep, 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + } + else + { + // add UDP ASSOCIATE header and forward to client + std::uint32_t const from_addr = m_udp_from.address().to_v4().to_uint(); + std::array header; + header[0] = 0; // RSV + header[1] = 0; + header[2] = 0; // fragment + header[3] = 1; // ATYP + header[4] = (from_addr >> 24) & 0xff; // Address + header[5] = (from_addr >> 16) & 0xff; + header[6] = (from_addr >> 8) & 0xff; + header[7] = (from_addr) & 0xff; + header[8] = (from_port >> 8) & 0xff; + header[9] = from_port & 0xff; + + std::array vec{{ + {header.data(), header.size()}, + {m_udp_buffer.data(), bytes_transferred}}}; + + error_code err; + m_udp_associate.send_to(vec, m_udp_associate_ep, 0, err); + if (err) std::printf("send_to failed: %s\n", err.message().c_str()); + } + } + + m_udp_associate.async_receive_from(boost::asio::buffer(m_udp_buffer) + , m_udp_from, 0, std::bind(&socks_connection::on_read_udp, this, std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::start_accept(boost::system::error_code const& ec) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_bind_socket.listen(); + m_bind_socket.async_accept(m_server_connection + , std::bind(&socks_connection::on_connected + , shared_from_this(), std::placeholders::_1)); + + m_client_connection.async_read_some( + sim::asio::buffer(m_out_buffer) + , std::bind(&socks_connection::on_client_receive, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + int socks_connection::format_response(address const& addr, int const port + , int const response) + { + int i = 0; + if (m_version == 5) + { +// +----+-----+-------+------+----------+----------+ +// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + m_in_buffer[i++] = char(m_version); // version + m_in_buffer[i++] = char(response); // response + m_in_buffer[i++] = 0; // reserved + if (addr.is_v4()) + { + m_in_buffer[i++] = 1; // IPv4 + address_v4::bytes_type b = addr.to_v4().to_bytes(); + memcpy(&m_in_buffer[i], &b[0], b.size()); + i += int(b.size()); + } else { + m_in_buffer[i++] = 4; // IPv6 + address_v6::bytes_type b = addr.to_v6().to_bytes(); + memcpy(&m_in_buffer[i], &b[0], b.size()); + i += int(b.size()); + } + + m_in_buffer[i++] = (port >> 8) & 0xff; + m_in_buffer[i++] = port & 0xff; + } + else + { + m_in_buffer[i++] = 0; // response version + m_in_buffer[i++] = char(response); // return code + + assert(addr.is_v4()); + + m_in_buffer[i++] = (port >> 8) & 0xff; + m_in_buffer[i++] = port & 0xff; + + address_v4::bytes_type b = addr.to_v4().to_bytes(); + memcpy(&m_in_buffer[i], &b[0], b.size()); + i += int(b.size()); + } + return i; + } + + int socks_connection::format_hostname_response(char const* hostname, int const port + , int const response) + { + int i = 0; + if (m_version != 5) + { + std::printf("socks_connection: hostname response requires SOCKS v5\n"); + close_connection(); + return 0; + } +// +----+-----+-------+------+----------+----------+ +// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +// +----+-----+-------+------+----------+----------+ +// | 1 | 1 | X'00' | 1 | Variable | 2 | +// +----+-----+-------+------+----------+----------+ + + m_in_buffer[i++] = char(m_version); // version + m_in_buffer[i++] = char(response); // response + m_in_buffer[i++] = 0; // reserved + m_in_buffer[i++] = 3; // DOMAINNAME + m_in_buffer[i++] = std::uint8_t(::strlen(hostname)); + for (; *hostname != '\0'; ++hostname) + m_in_buffer[i++] = *hostname; + + m_in_buffer[i++] = (port >> 8) & 0xff; + m_in_buffer[i++] = port & 0xff; + return i; + } + + void socks_connection::on_connected(boost::system::error_code const& ec) + { + std::printf("socks_connection(%s): on_connect: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + + if (ec == asio::error::operation_aborted + || ec == boost::system::errc::bad_file_descriptor) + { + return; + } + + boost::system::error_code err; + asio::ip::tcp::endpoint const ep = m_server_connection.remote_endpoint(err); + if (!err) + { + std::printf("socks_connection(%s): remote_endpoint: %s %d\n" + , command(), ep.address().to_string().c_str(), ep.port()); + } + + int const response = ec + ? (m_version == 4 ? 91 : 5) + : (m_version == 4 ? 90 : 0); + int const len = format_response(ep.address(), ep.port(), response); + + if (ec) + { + std::printf("socks_connection(%s): failed to connect to/accept from target server: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [=](boost::system::error_code const&, size_t) + { + self->close_connection(); + }); + return; + } + + auto self = shared_from_this(); + + asio::async_write(m_client_connection + , asio::buffer(&m_in_buffer[0], len) + , [this, self](boost::system::error_code const& ec, size_t) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + return; + } + + // read from the client and from the server + self->m_server_connection.async_read_some( + sim::asio::buffer(m_in_buffer) + , std::bind(&socks_connection::on_server_receive, self + , std::placeholders::_1, std::placeholders::_2)); + self->m_client_connection.async_read_some( + sim::asio::buffer(m_out_buffer) + , std::bind(&socks_connection::on_client_receive, self + , std::placeholders::_1, std::placeholders::_2)); + }); + } + + // we received some data from the client, forward it to the server + void socks_connection::on_client_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + // bad file descriptor means the socket has been closed. Whoever closed + // the socket will have opened a new one, we cannot call + // close_connection() + if (ec == asio::error::operation_aborted + || ec == boost::system::errc::bad_file_descriptor) + return; + + if (ec) + { + std::printf("socks_connection (%s): error reading from client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + asio::async_write(m_server_connection, asio::buffer(&m_out_buffer[0], bytes_transferred) + , std::bind(&socks_connection::on_client_forward, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_client_forward(error_code const& ec + , size_t /* bytes_transferred */) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to server: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_client_connection.async_read_some( + sim::asio::buffer(m_out_buffer) + , std::bind(&socks_connection::on_client_receive, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + // we received some data from the server, forward it to the server + void socks_connection::on_server_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred) + { + if (ec) + { + std::printf("socks_connection(%s): error reading from server: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + asio::async_write(m_client_connection, asio::buffer(&m_in_buffer[0], bytes_transferred) + , std::bind(&socks_connection::on_server_forward, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::on_server_forward(error_code const& ec + , size_t /* bytes_transferred */) + { + if (ec) + { + std::printf("socks_connection(%s): error writing to client: (%d) %s\n" + , command(), ec.value(), ec.message().c_str()); + close_connection(); + return; + } + + m_server_connection.async_read_some( + sim::asio::buffer(m_in_buffer) + , std::bind(&socks_connection::on_server_receive, shared_from_this() + , std::placeholders::_1, std::placeholders::_2)); + } + + void socks_connection::close_connection() + { + error_code err; + m_client_connection.close(err); + if (err) + { + std::printf("socks_connection::close: failed to close client connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + m_server_connection.close(err); + if (err) + { + std::printf("socks_connection::close: failed to close server connection (%d) %s\n" + , err.value(), err.message().c_str()); + } + + m_bind_socket.close(err); + if (err) + { + std::printf("socks_connection::close: failed to close bind socket (%d) %s\n" + , err.value(), err.message().c_str()); + } + } + + char const* socks_connection::command() const + { + switch (m_command) + { + case 1: return "CONNECT"; + case 2: return "BIND"; + case 3: return "UDP_ASSOCIATE"; + default: return "UNKNOWN"; + } + } +} + + diff --git a/simulation/libsimulator/src/tcp_socket.cpp b/simulation/libsimulator/src/tcp_socket.cpp new file mode 100644 index 0000000..173de68 --- /dev/null +++ b/simulation/libsimulator/src/tcp_socket.cpp @@ -0,0 +1,929 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/pcap.hpp" +#include "simulator/handler_allocator.hpp" + +#include +#include +#include // for printf + +#include "simulator/push_warnings.hpp" +#include +#include +#include "simulator/pop_warnings.hpp" + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +using namespace std::placeholders; + +namespace sim { +namespace asio { +namespace ip { + + tcp::socket::socket(io_context& ios) + : socket_base(ios) + , m_connect_timer(ios) + , m_recv_timer(ios) + {} + + tcp::socket::socket(socket&& s) + : socket_base(std::move(s)) + , m_connect_handler(std::move(s.m_connect_handler)) + , m_connect_timer(std::move(s.m_connect_timer)) + , m_mss(s.m_mss) + , m_send_handler(std::move(s.m_send_handler)) + , m_wait_send_handler(std::move(s.m_wait_send_handler)) + , m_send_buffer(std::move(s.m_send_buffer)) + , m_incoming_queue(std::move(s.m_incoming_queue)) + , m_queue_size(std::move(s.m_queue_size)) + , m_recv_handler(std::move(s.m_recv_handler)) + , m_wait_recv_handler(std::move(s.m_wait_recv_handler)) + , m_recv_buffer(std::move(s.m_recv_buffer)) + , m_recv_timer(std::move(s.m_recv_timer)) + , m_is_v4(std::move(s.m_is_v4)) + , m_recv_null_buffers(std::move(s.m_recv_null_buffers)) + , m_send_null_buffers(std::move(s.m_send_null_buffers)) + , m_channel(std::move(s.m_channel)) + , m_next_outgoing_seq(std::move(s.m_next_outgoing_seq)) + , m_next_incoming_seq(std::move(s.m_next_incoming_seq)) + , m_last_drop_seq(std::move(s.m_last_drop_seq)) + , m_cwnd(std::move(s.m_cwnd)) + , m_bytes_in_flight(std::move(s.m_bytes_in_flight)) + , m_reorder_buffer(std::move(s.m_reorder_buffer)) + , m_outstanding_packet_sizes(std::move(s.m_outstanding_packet_sizes)) + , m_outgoing_packets(std::move(s.m_outgoing_packets)) + { + if (m_forwarder) m_forwarder->reset(this); + s.m_forwarder.reset(); + s.m_open = false; + s.m_bound_to = ip::tcp::endpoint(); + + if (m_bound_to != ip::tcp::endpoint()) + m_io_service.rebind_socket(&s, this, m_bound_to); + } + + tcp::socket::~socket() + { + boost::system::error_code ec; + + m_channel.reset(); + + if (m_bound_to != ip::tcp::endpoint()) + { + m_io_service.unbind_socket(this, m_bound_to); + m_bound_to = ip::tcp::endpoint(); + m_user_bound_to = ip::tcp::endpoint(); + } + m_open = false; + + // prevent any more packets from being delivered to this socket + if (m_forwarder) + { + m_forwarder->reset(); + m_forwarder.reset(); + } + cancel(ec); + } + + void tcp::socket::open(tcp protocol, boost::system::error_code& ec) try + { + close(ec); + m_open = true; + m_is_v4 = (protocol == ip::tcp::v4()); + ec.clear(); + m_forwarder = std::make_shared(this); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void tcp::socket::open(tcp protocol) + { + boost::system::error_code ec; + open(protocol, ec); + if (ec) throw boost::system::system_error(ec); + } + + // used to attach an incoming connection to this + void tcp::socket::internal_connect(tcp::endpoint const& bind_ip + , std::shared_ptr const& c + , boost::system::error_code& ec) + { + open(m_is_v4 ? tcp::v4() : tcp::v6(), ec); + if (ec) + { + std::printf("tcp::socket::internal_connect() error: (%d) %s\n" + , ec.value(), ec.message().c_str()); + return; + } + m_bound_to = bind_ip; + m_user_bound_to = bind_ip; + m_channel = c; + assert(m_forwarder); + c->hops[1].replace_last(m_forwarder); + } + + void tcp::socket::bind(ip::tcp::endpoint const& ep + , boost::system::error_code& ec) try + { + if (!m_open) + { + ec = error::bad_descriptor; + return; + } + + if (ep.address().is_v4() != m_is_v4) + { + ec = error::address_family_not_supported; + return; + } + + ip::tcp::endpoint addr = m_io_service.bind_socket(this, ep, ec); + if (ec) return; + m_bound_to = addr; + m_user_bound_to = addr; + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void tcp::socket::bind(ip::tcp::endpoint const& ep) + { + boost::system::error_code ec; + bind(ep, ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::socket::close() + { + boost::system::error_code ec; + close(ec); + if (ec) throw boost::system::system_error(ec); + } + + void tcp::socket::close(boost::system::error_code& ec) try + { + if (m_channel) + { + int const remote = m_channel->remote_idx(m_bound_to); + route hops = m_channel->hops[remote]; + + // if m_connect_handler is still set, it means the connection hasn't + // been established yet, and this channel points to the acceptor + // socket, not another open TCP connection. + if (!hops.empty() && !m_connect_handler) + { + aux::packet p; + p.type = aux::packet::type_t::error; + p.ec = asio::error::eof; + p.from = asio::ip::udp::endpoint( + m_bound_to.address(), m_bound_to.port()); + p.overhead = 40; + p.hops = hops; + p.seq_nr = m_next_outgoing_seq++; + send_packet(std::move(p)); + } + m_channel.reset(); + } + + if (m_bound_to != ip::tcp::endpoint()) + { + m_io_service.unbind_socket(this, m_bound_to); + m_bound_to = ip::tcp::endpoint(); + m_user_bound_to = ip::tcp::endpoint(); + } + m_open = false; + + // prevent any more packets from being delivered to this socket + if (m_forwarder) + { + m_forwarder->reset(); + m_forwarder.reset(); + } + + // reset socket state + m_queue_size = 0; + m_mss = 1475; + m_cwnd = m_mss * 2; + m_bytes_in_flight = 0; + m_outstanding_packet_sizes.clear(); + m_recv_null_buffers = false; + m_send_null_buffers = false; + m_next_incoming_seq = 0; + m_next_outgoing_seq = 0; + m_last_drop_seq = 0; + + cancel(ec); + + ec.clear(); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + std::size_t tcp::socket::available(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + if (!m_channel) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + if (m_incoming_queue.empty()) + { + return 0; + } + + std::size_t ret = 0; + for (aux::packet const& p : m_incoming_queue) + { + if (p.type == aux::packet::type_t::error) + { + if (ret > 0) return ret; + + // if the read buffer is drained and there is an error, report that + // error. + ec = p.ec; + return 0; + } + ret += p.buffer.size(); + } + return ret; + } + + std::size_t tcp::socket::available() const + { + boost::system::error_code ec; + std::size_t ret = available(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + void tcp::socket::cancel(boost::system::error_code&) + { + abort_recv_handlers(); + abort_send_handlers(); + + if (m_connect_handler) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(m_connect_handler) + , boost::system::error_code(error::operation_aborted)))); + m_connect_handler = nullptr; + } + } + + void tcp::socket::cancel() + { + boost::system::error_code ec; + cancel(ec); + if (ec) throw boost::system::system_error(ec); + } + + tcp::endpoint tcp::socket::remote_endpoint(boost::system::error_code& ec) const + { + if (!m_open) + { + ec = error::bad_descriptor; + return tcp::endpoint(); + } + + if (!m_channel) + { + ec = error::not_connected; + return tcp::endpoint(); + } + + int const remote = m_channel->remote_idx(m_bound_to); + return m_channel->visible_ep[remote]; + } + + tcp::endpoint tcp::socket::remote_endpoint() const + { + boost::system::error_code ec; + tcp::endpoint ret = remote_endpoint(ec); + if (ec) throw boost::system::system_error(ec); + return ret; + } + + void tcp::socket::async_connect(tcp::endpoint const& target + , aux::function h) + { + if (!m_open) open(target.protocol()); + + assert(h); + assert(!m_connect_handler); + + // find remote socket + boost::system::error_code ec; + if (m_bound_to.address() == ip::address()) + { + auto endpoint = ip::tcp::endpoint(); + if (target.address().is_v4()) { + endpoint.address(ip::address_v4::any()); + } else { + endpoint.address(ip::address_v6::any()); + } + ip::tcp::endpoint addr = m_io_service.bind_socket(this + , endpoint, ec); + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(h), ec))); + return; + } + m_bound_to = addr; + m_user_bound_to = addr; + } + if (m_bound_to.address().is_v4() != target.address().is_v4()) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(h), + boost::system::error_code(error::address_family_not_supported)))); + return; + } + m_channel = m_io_service.internal_connect(this, target, ec); + m_mss = m_io_service.get_path_mtu(m_bound_to.address(), target.address()); + m_cwnd = m_mss * 2; + if (ec) + { + m_channel.reset(); + // TODO: ask the policy object what the round-trip to this endpoint is + m_connect_timer.expires_after(chrono::milliseconds(50)); + m_connect_timer.async_wait(aux::make_malloc(std::bind(std::move(h), ec))); + return; + } + + m_connect_handler = std::move(h); + + // the acceptor socket will call internal_connect_complete once the + // connection is established + } + + void tcp::socket::abort_recv_handlers() + { + if (m_recv_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_recv_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_recv_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_wait_recv_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_recv_timer.cancel(); + m_recv_handler = nullptr; + m_wait_recv_handler = nullptr; + m_recv_buffer.clear(); + m_recv_null_buffers = false; + } + + void tcp::socket::abort_send_handlers() + { + if (m_send_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_send_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_send_handler) post(m_io_service, aux::make_malloc(std::bind(std::move(m_wait_send_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_send_handler = nullptr; + m_wait_send_handler = nullptr; + m_send_buffer.clear(); + m_send_null_buffers = false; + } + + void tcp::socket::async_write_some_impl(std::vector const& bufs + , aux::function handler) + { + boost::system::error_code ec; + std::size_t const bytes_transferred = write_some_impl(bufs, ec); + if (ec == boost::system::error_code(error::would_block)) + { + m_send_handler = std::move(handler); + m_send_buffer = bufs; + return; + } + + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec, std::size_t(0)))); + m_send_handler = nullptr; + m_send_buffer.clear(); + return; + } + + boost::system::error_code no_error; + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), no_error + , bytes_transferred))); + m_send_handler = nullptr; + m_send_buffer.clear(); + } + + std::size_t tcp::socket::write_some_impl( + std::vector const& bufs + , boost::system::error_code& ec) + { + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + if (!m_channel) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + + // the connect handler is used as proxy that this socket has not competed + // the connection yet. We're still waiting for SYN+ACK + if (m_connect_handler) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + int const remote = m_channel->remote_idx(m_bound_to); + route hops = m_channel->hops[remote]; + if (hops.empty()) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + + if (m_bytes_in_flight + m_mss > m_cwnd) + { + // this indicates that the send buffer is very large, we should + // probably not be able to stuff more bytes down it + // wait for the receiving end to pop some bytes off + ec = boost::system::error_code(error::would_block); + return 0; + } + + std::size_t ret = 0; + + for (auto const& buf : bufs) + { + // split up in packets + int buf_size = int(buf.size()); + std::uint8_t const* ptr = static_cast(buf.data()); + while (buf_size > 0) + { + int packet_size = (std::min)(buf_size, m_mss); + aux::packet p; + p.type = aux::packet::type_t::payload; + p.buffer.assign(ptr, ptr + packet_size); + p.from = asio::ip::udp::endpoint( + m_bound_to.address(), m_bound_to.port()); + p.overhead = 40; + p.hops = hops; + p.seq_nr = m_next_outgoing_seq++; + p.drop_fun = std::bind(&tcp::socket::packet_dropped, this, _1); + + send_packet(std::move(p)); + ptr += packet_size; + buf_size -= packet_size; + ret += packet_size; + + if (m_bytes_in_flight + m_mss > m_cwnd) + { + // the congestion window is full + if (ret == 0) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + return ret; + } + } + } + + return ret; + } + + std::size_t tcp::socket::read_some_impl( + std::vector const& bufs + , boost::system::error_code& ec) + { + // a zero-sized read always completes immediately with 0 bytes; + // boost.asio's SSL implementation relies on this to post a + // completion handler without transferring any data. + if (boost::asio::buffer_size(bufs) == 0) + { + ec.clear(); + return 0; + } + + assert(!bufs.empty()); + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + if (!m_channel) + { + ec = boost::system::error_code(error::not_connected); + return 0; + } + if (m_connect_handler) + { + // the socket is not done connecting yet + ec = boost::system::error_code(error::would_block); + return 0; + } + + if (m_incoming_queue.empty()) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + typedef std::vector buffers_t; + m_recv_buffer = bufs; + buffers_t::iterator recv_iter = m_recv_buffer.begin(); + int total_received = 0; + // the offset in the current receive buffer we're writing to. i.e. the + // buffer recv_iter points to + int buf_offset = 0; + + while (!m_incoming_queue.empty()) + { + aux::packet& p = m_incoming_queue.front(); + + if (p.type == aux::packet::type_t::error) + { + // if we have received bytes also, first deliver those. In the next + // read, deliver the error + if (total_received > 0) break; + + assert(p.ec); + ec = p.ec; + m_incoming_queue.erase(m_incoming_queue.begin()); + m_channel.reset(); + return 0; + } + else if (p.type == aux::packet::type_t::payload) + { + // copy bytes from the incoming queue into the receive buffer. + // both are vectors of buffer, so it can get a bit hairy + while (recv_iter != m_recv_buffer.end()) + { + int const buf_size = int(recv_iter->size()); + int const copy_size = (std::min)(int(p.buffer.size()) + , buf_size - buf_offset); + + memcpy(static_cast(recv_iter->data()) + buf_offset + , p.buffer.data(), copy_size); + + p.buffer.erase(p.buffer.begin(), p.buffer.begin() + copy_size); + m_queue_size -= copy_size; + + buf_offset += copy_size; + assert(buf_offset <= buf_size); + total_received += copy_size; + if (buf_offset == buf_size) + { + ++recv_iter; + buf_offset = 0; + } + + if (p.buffer.empty()) + { + m_incoming_queue.erase(m_incoming_queue.begin()); + break; + } + } + } + else + { + assert(false); + } + + if (recv_iter == m_recv_buffer.end()) + break; + } + + assert(total_received > 0); + + ec.clear(); + return total_received; + } + + void tcp::socket::async_read_some_impl(std::vector const& bufs + , aux::function handler) + { + // a zero-sized read always completes immediately with 0 bytes + // transferred, regardless of connection or queue state. boost.asio's + // SSL implementation relies on this to bounce a completion handler + // through the executor without actually transferring any data. + if (boost::asio::buffer_size(bufs) == 0) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler) + , boost::system::error_code(), std::size_t(0)))); + return; + } + + assert(!bufs.empty()); + + boost::system::error_code ec; + std::size_t bytes_transferred = read_some_impl(bufs, ec); + if (ec == boost::system::error_code(error::would_block)) + { + assert(m_incoming_queue.empty()); + + m_recv_buffer = bufs; + m_recv_handler = std::move(handler); + m_recv_null_buffers = false; + return; + } + + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec, std::size_t(0)))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + return; + } + + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec, bytes_transferred))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + } + + void tcp::socket::async_wait_read_impl( + aux::function handler) + { + boost::system::error_code ec; + // null_buffers notifies the handler when data is available, without + // reading any + int const bytes = int(available(ec)); + if (ec) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + return; + } + + if (bytes > 0) + { + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + return; + } + + m_wait_recv_handler = std::move(handler); + m_recv_null_buffers = true; + } + + void tcp::socket::async_wait_write_impl( + aux::function handler) + { + if (!m_open) + { + boost::system::error_code const ec(error::bad_descriptor); + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec))); + return; + } + + if (!m_channel) + { + boost::system::error_code const ec(error::not_connected); + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec))); + return; + } + + if (!m_connect_handler && m_bytes_in_flight + m_mss <= m_cwnd) + { + // the socket is writable right now. complete immediately without + // actually writing anything, this is the null_buffers equivalent + // for writes + boost::system::error_code const ec; + post(m_io_service, aux::make_malloc(std::bind(std::move(handler), ec))); + return; + } + + m_wait_send_handler = std::move(handler); + m_send_null_buffers = true; + } + + // if there is an outstanding read operation, and this was the first incoming + // operation since we last drained, wake up the reader + void tcp::socket::maybe_wakeup_reader() + { + if (m_incoming_queue.size() != 1 || (!m_recv_handler && !m_wait_recv_handler)) return; + + if (m_recv_null_buffers) + { + async_wait_read_impl(std::move(m_wait_recv_handler)); + } + else + { + // we have an async. read operation outstanding, and we just put one + // packet in our incoming queue. + + // try to read from it and potentially fire the handler + async_read_some_impl(m_recv_buffer, std::move(m_recv_handler)); + } + } + + void tcp::socket::maybe_wakeup_writer() + { + if (!m_send_handler && !m_wait_send_handler) return; + + if (m_send_null_buffers) + { + async_wait_write_impl(std::move(m_wait_send_handler)); + } + else + { + // we have an async. write operation outstanding + async_write_some_impl(m_send_buffer, std::move(m_send_handler)); + } + } + + bool tcp::socket::internal_is_listening() { return false; } + + void tcp::socket::send_packet(aux::packet p) + { + m_bytes_in_flight += int(p.buffer.size()); + m_outstanding_packet_sizes[p.seq_nr] = int(p.buffer.size()); + + int const idx = m_channel->self_idx(m_bound_to); + p.byte_counter = m_channel->bytes_sent[idx]; + m_channel->bytes_sent[idx] += std::uint32_t(p.buffer.size()); + + auto* log = m_io_service.sim().get_pcap(); + if (log) + { + int const remote = m_channel->remote_idx(m_bound_to); + log->log_tcp(p, m_bound_to, m_channel->ep[remote]); + } + + forward_packet(std::move(p)); + } + + void tcp::socket::packet_dropped(aux::packet p) + { + int remote = m_channel->remote_idx(m_bound_to); + p.hops = m_channel->hops[remote]; + m_outgoing_packets.push_back(std::move(p)); + + const int packets_in_cwnd = m_cwnd / m_mss; + + // we just recently dropped a packet and cut the cwnd in half, + // don't do it again already + if (m_last_drop_seq > 0 && p.seq_nr < m_last_drop_seq + packets_in_cwnd) return; + + m_cwnd /= 2; + m_last_drop_seq = p.seq_nr; + + // TODO: this should really happen one second later to be accurate + if (m_cwnd < m_mss) m_cwnd = m_mss; + } + + void tcp::socket::incoming_packet(aux::packet p) + { + switch (p.type) + { + case aux::packet::type_t::uninitialized: + { + assert(false && "uninitialized packet"); + return; + } + case aux::packet::type_t::ack: + { + // if the socket just became writeable, we need to notify the + // client. First we want to know whether it was not writeable. + const bool was_writeable = m_bytes_in_flight + m_mss > m_cwnd; + + auto it = m_outstanding_packet_sizes.find(p.seq_nr); + assert(it != m_outstanding_packet_sizes.end()); + const int acked_bytes = it->second; + m_outstanding_packet_sizes.erase(it); + assert(m_bytes_in_flight >= acked_bytes); + m_bytes_in_flight -= acked_bytes; + + // potentially resend packets + while (!m_outgoing_packets.empty() + && m_bytes_in_flight + + int(m_outgoing_packets.front().buffer.size()) <= m_cwnd) + { + aux::packet pkt = std::move(m_outgoing_packets.front()); + m_outgoing_packets.erase(m_outgoing_packets.begin()); + send_packet(std::move(pkt)); + } + + // update cwnd based on the number of bytes ACKed. + // every round-trip, increase the window size by one packet + // (MSS) + m_cwnd += m_mss * acked_bytes / m_cwnd; + + // TODO: implement slow-start + + const bool is_writeable = m_bytes_in_flight + m_mss <= m_cwnd; + + if (!was_writeable && is_writeable) + maybe_wakeup_writer(); + + return; + } + case aux::packet::type_t::syn: + { + // TODO: return connection refused + return; + } + case aux::packet::type_t::syn_ack: + { + assert(m_connect_handler); + boost::system::error_code ec; + post(m_io_service, aux::make_malloc(std::bind(std::move(m_connect_handler), ec))); + m_connect_handler = nullptr; + if (ec) m_channel.reset(); + else maybe_wakeup_writer(); + return; + } + case aux::packet::type_t::error: + case aux::packet::type_t::payload: + { + aux::packet ack; + ack.type = aux::packet::type_t::ack; + ack.seq_nr = p.seq_nr; + + int remote = m_channel->remote_idx(m_bound_to); + ack.hops = m_channel->hops[remote]; + forward_packet(std::move(ack)); + + // if the sequence number is out-of-order, put it in the + // m_incoming_packets queue + if (p.seq_nr != m_next_incoming_seq) + { + if (p.seq_nr < m_next_incoming_seq) + { + std::printf("TCP: incoming sequence number lower (%" PRId64 ") " + "than expected: %" PRId64 "\n", p.seq_nr, m_next_incoming_seq); + } + + m_reorder_buffer.emplace(p.seq_nr, std::move(p)); + return; + } + + // this packet was in-order. increment the expected next sequence + // number. + ++m_next_incoming_seq; + m_incoming_queue.push_back(std::move(p)); + + // also, perhaps there are some packets that arrived out-of-order, + // check to see + auto it = m_reorder_buffer.find(m_next_incoming_seq); + while (it != m_reorder_buffer.end()) + { + aux::packet pkt = std::move(it->second); + m_reorder_buffer.erase(it); + m_incoming_queue.push_back(std::move(pkt)); + ++m_next_incoming_seq; + it = m_reorder_buffer.find(m_next_incoming_seq); + } + + maybe_wakeup_reader(); + return; + } + } + } +} +} +} + diff --git a/simulation/libsimulator/src/udp_socket.cpp b/simulation/libsimulator/src/udp_socket.cpp new file mode 100644 index 0000000..f735c72 --- /dev/null +++ b/simulation/libsimulator/src/udp_socket.cpp @@ -0,0 +1,492 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include "simulator/packet.hpp" +#include "simulator/pcap.hpp" +#include "simulator/handler_allocator.hpp" + +#include + +#include "simulator/push_warnings.hpp" +#include +#include +#include "simulator/pop_warnings.hpp" + +typedef sim::chrono::high_resolution_clock::time_point time_point; +typedef sim::chrono::high_resolution_clock::duration duration; + +namespace sim { +namespace asio { +namespace ip { + + udp::socket::socket(io_context& ios) + : socket_base(ios) + , m_next_send(chrono::high_resolution_clock::now()) + , m_recv_sender(nullptr) + , m_recv_timer(ios) + , m_send_timer(ios) + , m_recv_null_buffers(0) + , m_queue_size(0) + , m_is_v4(true) + {} + + udp::socket::socket(socket&& s) + : socket_base(std::move(s)) + , m_next_send(std::move(s.m_next_send)) + , m_send_handler(std::move(s.m_send_handler)) + , m_wait_send_handler(std::move(s.m_wait_send_handler)) + , m_recv_handler(std::move(s.m_recv_handler)) + , m_wait_recv_handler(std::move(s.m_wait_recv_handler)) + , m_recv_buffer(std::move(s.m_recv_buffer)) + , m_recv_sender(std::move(s.m_recv_sender)) + , m_recv_timer(std::move(s.m_recv_timer)) + , m_send_timer(std::move(s.m_send_timer)) + , m_incoming_queue(std::move(s.m_incoming_queue)) + , m_recv_null_buffers(std::move(s.m_recv_null_buffers)) + , m_queue_size(std::move(s.m_queue_size)) + , m_is_v4(s.m_is_v4) + { + if (m_forwarder) m_forwarder->reset(this); + s.m_forwarder.reset(); + s.m_open = false; + s.m_bound_to = ip::udp::endpoint(); + if (m_bound_to != ip::udp::endpoint()) + m_io_service.rebind_udp_socket(this, m_bound_to); + } + + udp::socket::~socket() + { + boost::system::error_code ec; + close(ec); + } + + void udp::socket::bind(ip::udp::endpoint const& ep + , boost::system::error_code& ec) try + { + if (!m_open) + { + ec = error::bad_descriptor; + return; + } + + if (ep.address().is_v4() != m_is_v4) + { + ec = error::address_family_not_supported; + return; + } + + ip::udp::endpoint addr = m_io_service.bind_udp_socket(this, ep, ec); + if (ec) return; + m_bound_to = addr; + m_user_bound_to = addr; + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void udp::socket::bind(ip::udp::endpoint const& ep) + { + boost::system::error_code ec; + bind(ep, ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::open(udp protocol + , boost::system::error_code& ec) try + { + // TODO: what if it's already open? + close(ec); + m_open = true; + m_is_v4 = (protocol == ip::udp::v4()); + m_forwarder = std::make_shared(this); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void udp::socket::open(udp protocol) + { + boost::system::error_code ec; + open(protocol, ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::close() + { + boost::system::error_code ec; + close(ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::close(boost::system::error_code& ec) try + { + if (m_bound_to != ip::udp::endpoint()) + { + m_io_service.unbind_udp_socket(this, m_bound_to); + m_bound_to = ip::udp::endpoint(); + m_user_bound_to = ip::udp::endpoint(); + } + m_open = false; + + // prevent any more packets from being delivered to this socket + if (m_forwarder) + { + m_forwarder->reset(); + m_forwarder.reset(); + } + + cancel(ec); + } + catch (std::bad_alloc const&) + { + ec = make_error_code(boost::system::errc::not_enough_memory); + } + catch (boost::system::system_error const& err) + { + ec = err.code(); + } + + void udp::socket::cancel(boost::system::error_code&) + { + // cancel outstanding async operations + abort_recv_handlers(); + abort_send_handlers(); + m_recv_timer.cancel(); + m_send_timer.cancel(); + return; + } + + void udp::socket::cancel() + { + boost::system::error_code ec; + cancel(ec); + if (ec) throw boost::system::system_error(ec); + } + + void udp::socket::abort_send_handlers() + { + if (m_send_handler) + post(m_io_service, make_malloc(std::bind(std::ref(m_send_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_send_handler) + post(m_io_service, make_malloc(std::bind(std::ref(m_wait_send_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_send_timer.cancel(); + m_send_handler = nullptr; + m_wait_send_handler = nullptr; +// m_send_buffer.clear(); + } + + void udp::socket::abort_recv_handlers() + { + if (m_recv_handler) + post(m_io_service, make_malloc(std::bind(std::move(m_recv_handler) + , boost::system::error_code(error::operation_aborted), std::size_t(0)))); + + if (m_wait_recv_handler) + post(m_io_service, make_malloc(std::bind(std::move(m_wait_recv_handler) + , boost::system::error_code(error::operation_aborted)))); + + m_recv_timer.cancel(); + m_recv_handler = nullptr; + m_wait_recv_handler = nullptr; + m_recv_buffer.clear(); + } + + void udp::socket::async_wait(socket_base::wait_type_t const w + , aux::function handler) + { + if (w == wait_type_t::wait_write) + { + abort_send_handlers(); + + time_point const now = chrono::high_resolution_clock::now(); + boost::system::error_code no_error; + if (m_next_send - now > m_send_queue_time / 2) + { + // our send queue is too large. Defer + m_recv_timer.expires_at(m_next_send + m_send_queue_time / 2); + + m_wait_send_handler = std::move(handler); + m_recv_timer.async_wait(make_malloc(std::bind(std::ref(m_wait_send_handler), no_error))); + return; + } + + // the socket is writable, post the completion handler immediately + post(m_io_service, make_malloc(std::bind(std::move(handler), no_error))); + } + else if (w == wait_type_t::wait_read) + { + abort_recv_handlers(); + async_wait_receive_impl(nullptr, std::move(handler)); + } + } + + std::size_t udp::socket::receive_from_impl( + std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags /* flags */ + , boost::system::error_code& ec) + { + assert(!bufs.empty()); + if (!m_open) + { + ec = boost::system::error_code(error::bad_descriptor); + return 0; + } + + if (m_bound_to == udp::endpoint()) + { + ec = boost::system::error_code(error::invalid_argument); + return 0; + } + + if (m_incoming_queue.empty()) + { + ec = boost::system::error_code(error::would_block); + return 0; + } + + aux::packet& p = m_incoming_queue.front(); + if (sender) *sender = p.from; + + int read = 0; + for (auto const& buf : bufs) + { + char* ptr = static_cast(buf.data()); + int const len = int(buf.size()); + int const to_copy = (std::min)(int(p.buffer.size()), len); + memcpy(ptr, p.buffer.data(), to_copy); + read += to_copy; + p.buffer.erase(p.buffer.begin(), p.buffer.begin() + to_copy); + m_queue_size -= to_copy; + if (p.buffer.empty()) break; + } + + m_incoming_queue.erase(m_incoming_queue.begin()); + return read; + } + + void udp::socket::async_wait_receive_impl( + udp::endpoint* sender + , aux::function handler) + { + if (!m_open) + { + post(m_io_service, make_malloc(std::bind(std::move(handler) + , boost::system::error_code(error::bad_descriptor)))); + return; + } + + if (m_bound_to == udp::endpoint()) + { + post(m_io_service, make_malloc(std::bind(std::move(handler) + , boost::system::error_code(error::invalid_argument)))); + return; + } + + if (!m_incoming_queue.empty()) + { + post(m_io_service, make_malloc(std::bind(std::move(handler), boost::system::error_code()))); + return; + } + + m_recv_null_buffers = true; + m_wait_recv_handler = std::move(handler); + m_recv_sender = sender; + } + + void udp::socket::async_receive_from_impl( + std::vector const& bufs + , udp::endpoint* sender + , socket_base::message_flags /* flags */ + , aux::function handler) + { + assert(!bufs.empty()); + + boost::system::error_code ec; + std::size_t bytes_transferred = receive_from_impl(bufs, sender, 0, ec); + if (ec == boost::system::error_code(error::would_block)) + { + m_recv_buffer = bufs; + m_recv_handler = std::move(handler); + m_recv_sender = sender; + m_recv_null_buffers = false; + + return; + } + + if (ec) + { + post(m_io_service, make_malloc(std::bind(std::move(handler), ec, std::size_t(0)))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + m_recv_sender = nullptr; + m_recv_null_buffers = false; + return; + } + + post(m_io_service, make_malloc(std::bind(std::move(handler), ec, bytes_transferred))); + m_recv_handler = nullptr; + m_recv_buffer.clear(); + m_recv_sender = nullptr; + m_recv_null_buffers = false; + } + + std::size_t udp::socket::send_to_impl(std::vector const& b + , udp::endpoint const& dst, message_flags /* flags */ + , boost::system::error_code& ec) + { + assert(m_non_blocking && "blocking operations not supported"); + + if (m_bound_to == ip::udp::endpoint()) + { + // the socket was not bound, bind to anything + bind(udp::endpoint(), ec); + if (ec) return 0; + } + + ec.clear(); + std::size_t ret = 0; + for (std::vector::const_iterator i = b.begin() + , end(b.end()); i != end; ++i) + { + ret += i->size(); + } + if (ret == 0) + { + ec = boost::system::error_code(error::invalid_argument); + return 0; + } + + time_point now = chrono::high_resolution_clock::now(); + + const int mtu = m_io_service.get_path_mtu(m_bound_to.address(), dst.address()); + + if (int(ret) > 65535) + { + ec = boost::system::error_code(error::message_size); + return 0; + } + + if (m_dont_fragment && int(ret) > mtu) + { + // silently drop packet + ec.clear(); + return ret; + } + + // determine the bandwidth in terms of nanoseconds / byte + const double nanoseconds_per_byte = 1000000000.0 + / double(aux::nic_bandwidth); + + if (m_next_send - now > m_send_queue_time) + { + // our send queue is too large. + ec = boost::system::error_code(asio::error::would_block); + return 0; + } + + route hops = m_io_service.find_udp_socket(*this, dst); + if (hops.empty()) + { + // the packet is silently dropped + // TODO: it would be nice if this would result in a round-trip time + // with an ICMP host unreachable or connection_refused error + return ret; + } + + hops.prepend(m_io_service.get_outgoing_route(m_bound_to.address())); + + m_next_send = std::max(now, m_next_send); + + aux::packet p; + p.overhead = 28; + p.type = aux::packet::type_t::payload; + p.from = m_bound_to; + p.hops = hops; + for (std::vector::const_iterator i = b.begin() + , end(b.end()); i != end; ++i) + { + p.buffer.insert(p.buffer.end(), static_cast(i->data()) + , static_cast(i->data()) + i->size()); + } + + auto* log = m_io_service.sim().get_pcap(); + if (log) log->log_udp(p, m_bound_to, dst); + + int const packet_size = int(p.buffer.size() + p.overhead); + forward_packet(std::move(p)); + + m_next_send += chrono::duration_cast(chrono::nanoseconds( + boost::int64_t(nanoseconds_per_byte * packet_size))); + + return ret; + } + + void udp::socket::incoming_packet(aux::packet p) + { + int const packet_size = int(p.buffer.size() + p.overhead); + + // silent drop. If the application isn't reading fast enough, drop packets + // TODO: make this limit controlled by SO_RECVBUF socket option + if (m_queue_size + packet_size > 256 * 1024) return; + + m_queue_size += int(p.buffer.size()); + m_incoming_queue.push_back(std::move(p)); + + maybe_wakeup_reader(); + } + + void udp::socket::maybe_wakeup_reader() + { + if (m_incoming_queue.size() != 1 || (!m_recv_handler && !m_wait_recv_handler)) return; + + // there is an outstanding operation waiting for an incoming packet + if (m_recv_null_buffers) + { + async_wait_receive_impl(m_recv_sender, std::move(m_wait_recv_handler)); + } + else + { + async_receive_from_impl(m_recv_buffer, m_recv_sender, 0, std::move(m_recv_handler)); + } + +// m_recv_handler = nullptr; +// m_recv_buffer.clear(); +// m_recv_sender = nullptr; + } + +} // ip +} // asio +} // sim + diff --git a/simulation/libsimulator/test/acceptor.cpp b/simulation/libsimulator/test/acceptor.cpp new file mode 100644 index 0000000..6c3c34b --- /dev/null +++ b/simulation/libsimulator/test/acceptor.cpp @@ -0,0 +1,183 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "simulator/simulator.hpp" +#include + +#include "catch.hpp" + +#ifdef __GNUC__ +// for CATCH's CHECK macro +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + +using namespace sim::asio; +using namespace sim::chrono; +using sim::simulation; +using sim::default_config; +using namespace std::placeholders; + +namespace { +char send_buffer[10000]; +char recv_buffer[10000]; +int num_received = 0; +int num_sent = 0; + +void on_sent(boost::system::error_code const& ec, std::size_t bytes_transferred + , ip::tcp::socket& sock) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] send error %s\n", millis, ec.message().c_str()); + return; + } + + num_sent += int(bytes_transferred); + + std::printf("[%4d] sent %d bytes\n", millis, int(bytes_transferred)); + std::printf("closing\n"); + sock.close(); +} + +void on_receive(boost::system::error_code const& ec + , std::size_t bytes_transferred, ip::tcp::socket& sock) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] receive error %s\n", millis, ec.message().c_str()); + return; + } + + num_received += int(bytes_transferred); + + std::printf("[%4d] received %d bytes\n", millis, int(bytes_transferred)); + + sock.async_read_some(sim::asio::buffer(recv_buffer, sizeof(recv_buffer)) + , std::bind(&on_receive, _1, _2, std::ref(sock))); +} + +void incoming_connection(boost::system::error_code const& ec + , ip::tcp::socket& sock, ip::tcp::endpoint const& ep) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] error while accepting connection: %s\n" + , millis, ec.message().c_str()); + return; + } + + boost::system::error_code err; + ip::tcp::endpoint remote_endpoint = sock.remote_endpoint(err); + REQUIRE(!ec); + ip::tcp::endpoint local_endpoint = sock.local_endpoint(err); + REQUIRE(!ec); + std::printf("[%4d] received incoming connection from: %s:%d. local endpoint: %s:%d\n" + , millis, ep.address().to_string().c_str(), ep.port() + , local_endpoint.address().to_string().c_str(), local_endpoint.port()); + CHECK(local_endpoint.port() == 1337); + CHECK(local_endpoint.address().to_string() == "40.30.20.10"); + CHECK(remote_endpoint.port() != 0); + CHECK(remote_endpoint.address().to_string() == "10.20.30.40"); + + sock.async_read_some(sim::asio::buffer(recv_buffer, sizeof(recv_buffer)) + , std::bind(&on_receive, _1, _2, std::ref(sock))); +} + +void on_connected(boost::system::error_code const& ec + , ip::tcp::socket& sock) +{ + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + if (ec) + { + std::printf("[%4d] error while connecting: %s\n", millis, ec.message().c_str()); + return; + } + + boost::system::error_code err; + ip::tcp::endpoint remote_endpoint = sock.remote_endpoint(err); + REQUIRE(!ec); + ip::tcp::endpoint local_endpoint = sock.local_endpoint(err); + REQUIRE(!ec); + + std::printf("[%4d] made outgoing connection to: %s:%d. local endpoint: %s:%d\n" + , millis + , remote_endpoint.address().to_string().c_str(), remote_endpoint.port() + , local_endpoint.address().to_string().c_str(), local_endpoint.port()); + + CHECK(remote_endpoint.port() == 1337); + CHECK(remote_endpoint.address().to_string() == "40.30.20.10"); + CHECK(local_endpoint.port() != 0); + CHECK(local_endpoint.address().to_string() == "10.20.30.40"); + + std::printf("sending %d bytes\n", int(sizeof(send_buffer))); + sock.async_write_some(sim::asio::buffer(send_buffer, sizeof(send_buffer)) + , std::bind(&on_sent, _1, _2, std::ref(sock))); +} + +} + +TEST_CASE("accept incoming connection on acceptor socket", "[acceptor]") +{ + default_config cfg; + simulation sim(cfg); + io_context incoming_ios(sim, ip::make_address_v4("40.30.20.10")); + io_context outgoing_ios(sim, ip::make_address_v4("10.20.30.40")); + ip::tcp::acceptor listener(incoming_ios); + + int millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + + boost::system::error_code ec; + listener.open(ip::tcp::v4(), ec); + REQUIRE(!ec); + listener.bind(ip::tcp::endpoint(ip::address(), 1337), ec); + REQUIRE(!ec); + listener.listen(10, ec); + REQUIRE(!ec); + + ip::tcp::socket incoming(incoming_ios); + ip::tcp::endpoint remote_endpoint; + listener.async_accept(incoming, remote_endpoint + , std::bind(&incoming_connection, _1, std::ref(incoming) + , std::cref(remote_endpoint))); + + dump_network_graph(sim, "accept.dot"); + + std::printf("[%4d] connecting\n", millis); + ip::tcp::socket outgoing(outgoing_ios); + outgoing.open(ip::tcp::v4(), ec); + REQUIRE(!ec); + outgoing.async_connect(ip::tcp::endpoint(ip::make_address("40.30.20.10") + , 1337), std::bind(&on_connected, _1, std::ref(outgoing))); + + sim.run(); + + millis = int(duration_cast(high_resolution_clock::now() + .time_since_epoch()).count()); + + CHECK(num_received == num_sent); + CHECK(num_received > 0); +} + diff --git a/simulation/libsimulator/test/catch.hpp b/simulation/libsimulator/test/catch.hpp new file mode 100644 index 0000000..6cc67e7 --- /dev/null +++ b/simulation/libsimulator/test/catch.hpp @@ -0,0 +1,14057 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.6.0 +// Generated: 2024-05-05 20:53:27.071502 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- +#ifndef CATCH_AMALGAMATED_HPP_INCLUDED +#define CATCH_AMALGAMATED_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2. It includes **all** of Catch2 headers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the top level folder, or to the + * corresponding internal subfolder, it should be added here. Headers + * added to the various subparts (e.g. matchers, generators, etc...), + * should go their respective catch-all headers. + */ + +#ifndef CATCH_ALL_HPP_INCLUDED +#define CATCH_ALL_HPP_INCLUDED + + + +/** \file + * This is a convenience header for Catch2's benchmarking. It includes + * **all** of Catch2 headers related to benchmarking. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `benchmark` folder, or to + * the corresponding internal (detail) subfolder, it should be added here. + */ + +#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED +#define CATCH_BENCHMARK_ALL_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_HPP_INCLUDED +#define CATCH_BENCHMARK_HPP_INCLUDED + + + +#ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +#define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + + + +#ifndef CATCH_PLATFORM_HPP_INCLUDED +#define CATCH_PLATFORM_HPP_INCLUDED + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# ifndef __has_extension +# define __has_extension(x) 0 +# endif +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS + +# if defined( WINAPI_FAMILY ) && ( WINAPI_FAMILY == WINAPI_FAMILY_APP ) +# define CATCH_PLATFORM_WINDOWS_UWP +# endif + +#elif defined(__ORBIS__) || defined(__PROSPERO__) +# define CATCH_PLATFORM_PLAYSTATION + +#endif + +#endif // CATCH_PLATFORM_HPP_INCLUDED + +#ifdef __cplusplus + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +# define CATCH_CPP20_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +// This only works on GCC 9+. so we have to also add a global suppression of Wparentheses +// for older versions of GCC. +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + _Pragma( "GCC diagnostic ignored \"-Wunused-result\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wuseless-cast\"" ) + +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wshadow\"" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__NVCOMPILER) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "diag push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "diag pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress declared_but_not_referenced" ) +#endif + +#if defined(__CUDACC__) && !defined(__clang__) +# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +// New pragmas introduced in CUDA 11.5+ +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "nv_diag_suppress 177" ) +# else +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress 177" ) +# endif +#endif + +// clang-cl defines _MSC_VER as well as __clang__, which could cause the +// start/stop internal suppression macros to be double defined. +#if defined(__clang__) && !defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ && !_MSC_VER + +#if defined(__clang__) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which +// results in calls to the immediately evaluated lambda expressions to be +// reported as unevaluated lambdas. +// https://developer.nvidia.com/nvidia_bug/3321845. +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) && !defined( __NVCOMPILER ) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wcomma\"" ) + +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wshadow\"" ) + +#endif // __clang__ + + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined( CATCH_PLATFORM_WINDOWS ) || \ + defined( CATCH_PLATFORM_PLAYSTATION ) || \ + defined( __CYGWIN__ ) || \ + defined( __QNX__ ) || \ + defined( __EMSCRIPTEN__ ) || \ + defined( __DJGPP__ ) || \ + defined( __OS400__ ) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#else +# define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Assume that some platforms do not support getenv. +#if defined( CATCH_PLATFORM_WINDOWS_UWP ) || \ + defined( CATCH_PLATFORM_PLAYSTATION ) || \ + defined( _GAMING_XBOX ) +# define CATCH_INTERNAL_CONFIG_NO_GETENV +#else +# define CATCH_INTERNAL_CONFIG_GETENV +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +// We want to defer to nvcc-specific warning suppression if we are compiled +// with nvcc masquerading for MSVC. +# if !defined( __CUDACC__ ) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + __pragma( warning( push ) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + __pragma( warning( pop ) ) +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(CATCH_PLATFORM_WINDOWS_UWP) +# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + + +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GETENV) && !defined(CATCH_INTERNAL_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_GETENV) +# define CATCH_CONFIG_GETENV +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined( CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED ) && \ + !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) && \ + !defined( CATCH_CONFIG_NO_DISABLE_EXCEPTIONS ) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS +#endif + + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +#if defined( CATCH_PLATFORM_WINDOWS ) && \ + !defined( CATCH_CONFIG_COLOUR_WIN32 ) && \ + !defined( CATCH_CONFIG_NO_COLOUR_WIN32 ) && \ + !defined( CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 ) +# define CATCH_CONFIG_COLOUR_WIN32 +#endif + +#if defined( CATCH_CONFIG_SHARED_LIBRARY ) && defined( _MSC_VER ) && \ + !defined( CATCH_CONFIG_STATIC ) +# ifdef Catch2_EXPORTS +# define CATCH_EXPORT //__declspec( dllexport ) // not needed +# else +# define CATCH_EXPORT __declspec( dllimport ) +# endif +#else +# define CATCH_EXPORT +#endif + +#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + + +#ifndef CATCH_CONTEXT_HPP_INCLUDED +#define CATCH_CONTEXT_HPP_INCLUDED + + +namespace Catch { + + class IResultCapture; + class IConfig; + + class Context { + IConfig const* m_config = nullptr; + IResultCapture* m_resultCapture = nullptr; + + CATCH_EXPORT static Context* currentContext; + friend Context& getCurrentMutableContext(); + friend Context const& getCurrentContext(); + static void createContext(); + friend void cleanUpContext(); + + public: + IResultCapture* getResultCapture() const { return m_resultCapture; } + IConfig const* getConfig() const { return m_config; } + void setResultCapture( IResultCapture* resultCapture ); + void setConfig( IConfig const* config ); + }; + + Context& getCurrentMutableContext(); + + inline Context const& getCurrentContext() { + // We duplicate the logic from `getCurrentMutableContext` here, + // to avoid paying the call overhead in debug mode. + if ( !Context::currentContext ) { Context::createContext(); } + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *Context::currentContext; + } + + void cleanUpContext(); + + class SimplePcg32; + SimplePcg32& sharedRng(); +} + +#endif // CATCH_CONTEXT_HPP_INCLUDED + + +#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED +#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + +#include + +//! Replacement for std::move with better compile time performance +#define CATCH_MOVE(...) static_cast&&>(__VA_ARGS__) + +//! Replacement for std::forward with better compile time performance +#define CATCH_FORWARD(...) static_cast(__VA_ARGS__) + +#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + + +#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED +#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + +namespace Catch { + + //! Used to signal that an assertion macro failed + struct TestFailureException{}; + //! Used to signal that the remainder of a test should be skipped + struct TestSkipException {}; + + /** + * Outlines throwing of `TestFailureException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_failure_exception(); + + /** + * Outlines throwing of `TestSkipException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_skip_exception(); + +} // namespace Catch + +#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED +#define CATCH_UNIQUE_NAME_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CONFIG configuration option + * + * When generating internal unique names, there are two options. Either + * we mix in the current line number, or mix in an incrementing number. + * We prefer the latter, using `__COUNTER__`, but users might want to + * use the former. + */ + +#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define CATCH_CONFIG_COUNTER_HPP_INCLUDED + + +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#if defined( CATCH_INTERNAL_CONFIG_COUNTER ) && \ + !defined( CATCH_CONFIG_NO_COUNTER ) && \ + !defined( CATCH_CONFIG_COUNTER ) +# define CATCH_CONFIG_COUNTER +#endif + + +#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_CAPTURE_HPP_INCLUDED +#define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + +#include +#include + + + +#ifndef CATCH_STRINGREF_HPP_INCLUDED +#define CATCH_STRINGREF_HPP_INCLUDED + +#include +#include +#include +#include + +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + static constexpr size_type npos{ static_cast( -1 ) }; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef other ) const noexcept -> bool { + return m_size == other.m_size + && (std::memcmp( m_start, other.m_start, m_size ) == 0); + } + auto operator != (StringRef other) const noexcept -> bool { + return !(*this == other); + } + + constexpr auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + bool operator<(StringRef rhs) const noexcept; + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + constexpr StringRef substr(size_type start, size_type length) const noexcept { + if (start < m_size) { + const auto shortened_size = m_size - start; + return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); + } else { + return StringRef(); + } + } + + // Returns the current start pointer. May not be null-terminated. + constexpr char const* data() const noexcept { + return m_start; + } + + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + + + friend std::string& operator += (std::string& lhs, StringRef rhs); + friend std::ostream& operator << (std::ostream& os, StringRef str); + friend std::string operator+(StringRef lhs, StringRef rhs); + + /** + * Provides a three-way comparison with rhs + * + * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive + * number if lhs > rhs + */ + int compare( StringRef rhs ) const; + }; + + + constexpr auto operator ""_sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator ""_catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +#endif // CATCH_STRINGREF_HPP_INCLUDED + + +#ifndef CATCH_RESULT_TYPE_HPP_INCLUDED +#define CATCH_RESULT_TYPE_HPP_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + // TODO: Should explicit skip be considered "not OK" (cf. isOk)? I.e., should it have the failure bit? + ExplicitSkip = 4, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); + + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); + + bool shouldContinueOnFailure( int flags ); + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ); + +} // end namespace Catch + +#endif // CATCH_RESULT_TYPE_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_PTR_HPP_INCLUDED +#define CATCH_UNIQUE_PTR_HPP_INCLUDED + +#include +#include + + +namespace Catch { +namespace Detail { + /** + * A reimplementation of `std::unique_ptr` for improved compilation performance + * + * Does not support arrays nor custom deleters. + */ + template + class unique_ptr { + T* m_ptr; + public: + constexpr unique_ptr(std::nullptr_t = nullptr): + m_ptr{} + {} + explicit constexpr unique_ptr(T* ptr): + m_ptr(ptr) + {} + + template ::value>> + unique_ptr(unique_ptr&& from): + m_ptr(from.release()) + {} + + template ::value>> + unique_ptr& operator=(unique_ptr&& from) { + reset(from.release()); + + return *this; + } + + unique_ptr(unique_ptr const&) = delete; + unique_ptr& operator=(unique_ptr const&) = delete; + + unique_ptr(unique_ptr&& rhs) noexcept: + m_ptr(rhs.m_ptr) { + rhs.m_ptr = nullptr; + } + unique_ptr& operator=(unique_ptr&& rhs) noexcept { + reset(rhs.release()); + + return *this; + } + + ~unique_ptr() { + delete m_ptr; + } + + T& operator*() { + assert(m_ptr); + return *m_ptr; + } + T const& operator*() const { + assert(m_ptr); + return *m_ptr; + } + T* operator->() noexcept { + assert(m_ptr); + return m_ptr; + } + T const* operator->() const noexcept { + assert(m_ptr); + return m_ptr; + } + + T* get() { return m_ptr; } + T const* get() const { return m_ptr; } + + void reset(T* ptr = nullptr) { + delete m_ptr; + m_ptr = ptr; + } + + T* release() { + auto temp = m_ptr; + m_ptr = nullptr; + return temp; + } + + explicit operator bool() const { + return m_ptr; + } + + friend void swap(unique_ptr& lhs, unique_ptr& rhs) { + auto temp = lhs.m_ptr; + lhs.m_ptr = rhs.m_ptr; + rhs.m_ptr = temp; + } + }; + + //! Specialization to cause compile-time error for arrays + template + class unique_ptr; + + template + unique_ptr make_unique(Args&&... args) { + return unique_ptr(new T(CATCH_FORWARD(args)...)); + } + + +} // end namespace Detail +} // end namespace Catch + +#endif // CATCH_UNIQUE_PTR_HPP_INCLUDED + + +#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_CLOCK_HPP_INCLUDED +#define CATCH_CLOCK_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + using IDuration = std::chrono::nanoseconds; + using FDuration = std::chrono::duration; + + template + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CLOCK_HPP_INCLUDED + +namespace Catch { + + // We cannot forward declare the type with default template argument + // multiple times, so it is split out into a separate header so that + // we can prevent multiple declarations in dependees + template + struct BenchmarkStats; + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct MessageBuilder; + struct Counts; + struct AssertionReaction; + struct SourceLineInfo; + + class ITransientExpression; + class IGeneratorTracker; + + struct BenchmarkInfo; + + namespace Generators { + class GeneratorUntypedBase; + using GeneratorBasePtr = Catch::Detail::unique_ptr; + } + + + class IResultCapture { + public: + virtual ~IResultCapture(); + + virtual void notifyAssertionStarted( AssertionInfo const& info ) = 0; + virtual bool sectionStarted( StringRef sectionName, + SourceLineInfo const& sectionLineInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo&& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo&& endInfo ) = 0; + + virtual IGeneratorTracker* + acquireGeneratorTracker( StringRef generatorName, + SourceLineInfo const& lineInfo ) = 0; + virtual IGeneratorTracker* + createGeneratorTracker( StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr&& generator ) = 0; + + virtual void benchmarkPreparing( StringRef name ) = 0; + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; + virtual void benchmarkFailed( StringRef error ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + StringRef message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string&& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_CONFIG_HPP_INCLUDED +#define CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_NONCOPYABLE_HPP_INCLUDED +#define CATCH_NONCOPYABLE_HPP_INCLUDED + +namespace Catch { + namespace Detail { + + //! Deriving classes become noncopyable and nonmovable + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable&& ) = delete; + NonCopyable& operator=( NonCopyable const& ) = delete; + NonCopyable& operator=( NonCopyable&& ) = delete; + + protected: + NonCopyable() noexcept = default; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_NONCOPYABLE_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + //! A test case or leaf section did not run any assertions + NoAssertions = 0x01, + //! A command line test spec matched no test cases + UnmatchedTestSpec = 0x02, + }; }; + + enum class ShowDurations { + DefaultForReporter, + Always, + Never + }; + enum class TestRunOrder { + Declared, + LexicographicallySorted, + Randomized + }; + enum class ColourMode : std::uint8_t { + //! Let Catch2 pick implementation based on platform detection + PlatformDefault, + //! Use ANSI colour code escapes + ANSI, + //! Use Win32 console colour API + Win32, + //! Don't use any colour + None + }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + class IStream; + + class IConfig : public Detail::NonCopyable { + public: + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual StringRef name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool zeroTestsCountAsSuccess() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations showDurations() const = 0; + virtual double minDuration() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector const& getTestsOrTags() const = 0; + virtual TestRunOrder runOrder() const = 0; + virtual uint32_t rngSeed() const = 0; + virtual unsigned int shardCount() const = 0; + virtual unsigned int shardIndex() const = 0; + virtual ColourMode defaultColourMode() const = 0; + virtual std::vector const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + + virtual bool skipBenchmarks() const = 0; + virtual bool benchmarkNoAnalysis() const = 0; + virtual unsigned int benchmarkSamples() const = 0; + virtual double benchmarkConfidenceInterval() const = 0; + virtual unsigned int benchmarkResamples() const = 0; + virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; + }; +} + +#endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED +#define CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + + +#include + +namespace Catch { + + class TestCaseHandle; + struct TestCaseInfo; + class ITestCaseRegistry; + class IExceptionTranslatorRegistry; + class IExceptionTranslator; + class ReporterRegistry; + class IReporterFactory; + class ITagAliasRegistry; + class ITestInvoker; + class IMutableEnumValuesRegistry; + struct SourceLineInfo; + + class StartupExceptionRegistry; + class EventListenerFactory; + + using IReporterFactoryPtr = Detail::unique_ptr; + + class IRegistryHub { + public: + virtual ~IRegistryHub(); // = default + + virtual ReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + class IMutableRegistryHub { + public: + virtual ~IMutableRegistryHub(); // = default + virtual void registerReporter( std::string const& name, IReporterFactoryPtr factory ) = 0; + virtual void registerListener( Detail::unique_ptr factory ) = 0; + virtual void registerTest(Detail::unique_ptr&& testInfo, Detail::unique_ptr&& invoker) = 0; + virtual void registerTranslator( Detail::unique_ptr&& translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +#endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + + +#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_HPP_INCLUDED +#define CATCH_ESTIMATE_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + template + struct Estimate { + Type point; + Type lower_bound; + Type upper_bound; + double confidence_interval; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED +#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + struct OutlierClassification { + int samples_seen = 0; + int low_severe = 0; // more than 3 times IQR below Q1 + int low_mild = 0; // 1.5 to 3 times IQR below Q1 + int high_mild = 0; // 1.5 to 3 times IQR above Q3 + int high_severe = 0; // more than 3 times IQR above Q3 + + int total() const { + return low_severe + low_mild + high_mild + high_severe; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED +// The fwd decl & default specialization needs to be seen by VS2017 before +// BenchmarkStats itself, or VS2017 will report compilation error. + +#include +#include + +namespace Catch { + + struct BenchmarkInfo { + std::string name; + double estimatedDuration; + int iterations; + unsigned int samples; + unsigned int resamples; + double clockResolution; + double clockCost; + }; + + // We need to keep template parameter for backwards compatibility, + // but we also do not want to use the template paraneter. + template + struct BenchmarkStats { + BenchmarkInfo info; + + std::vector samples; + Benchmark::Estimate mean; + Benchmark::Estimate standardDeviation; + Benchmark::OutlierClassification outliers; + double outlierVariance; + }; + + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED +#define CATCH_ENVIRONMENT_HPP_INCLUDED + + +namespace Catch { + namespace Benchmark { + struct EnvironmentEstimate { + FDuration mean; + OutlierClassification outliers; + }; + struct Environment { + EnvironmentEstimate clock_resolution; + EnvironmentEstimate clock_cost; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ENVIRONMENT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED +#define CATCH_EXECUTION_PLAN_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED +#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_CHRONOMETER_HPP_INCLUDED +#define CATCH_CHRONOMETER_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_OPTIMIZER_HPP_INCLUDED +#define CATCH_OPTIMIZER_HPP_INCLUDED + +#if defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) +# include // atomic_thread_fence +#endif + + +#include + +namespace Catch { + namespace Benchmark { +#if defined(__GNUC__) || defined(__clang__) + template + inline void keep_memory(T* p) { + asm volatile("" : : "g"(p) : "memory"); + } + inline void keep_memory() { + asm volatile("" : : : "memory"); + } + + namespace Detail { + inline void optimizer_barrier() { keep_memory(); } + } // namespace Detail +#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) + +#if defined(_MSVC_VER) +#pragma optimize("", off) +#elif defined(__IAR_SYSTEMS_ICC__) +// For IAR the pragma only affects the following function +#pragma optimize=disable +#endif + template + inline void keep_memory(T* p) { + // thanks @milleniumbug + *reinterpret_cast(p) = *reinterpret_cast(p); + } + // TODO equivalent keep_memory() +#if defined(_MSVC_VER) +#pragma optimize("", on) +#endif + + namespace Detail { + inline void optimizer_barrier() { + std::atomic_thread_fence(std::memory_order_seq_cst); + } + } // namespace Detail + +#endif + + template + inline void deoptimize_value(T&& x) { + keep_memory(&x); + } + + template + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { + deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...)); + } + + template + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t::value> { + CATCH_FORWARD((fn)) (CATCH_FORWARD(args)...); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OPTIMIZER_HPP_INCLUDED + + +#ifndef CATCH_META_HPP_INCLUDED +#define CATCH_META_HPP_INCLUDED + +#include + +namespace Catch { + template + struct true_given : std::true_type {}; + + struct is_callable_tester { + template + static true_given()(std::declval()...))> test(int); + template + static std::false_type test(...); + }; + + template + struct is_callable; + + template + struct is_callable : decltype(is_callable_tester::test(0)) {}; + + +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. + template + using FunctionReturnType = std::remove_reference_t>>; +#else + template + using FunctionReturnType = std::remove_reference_t>>; +#endif + +} // namespace Catch + +namespace mpl_{ + struct na; +} + +#endif // CATCH_META_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept(); // = default; + + ChronometerConcept() = default; + ChronometerConcept(ChronometerConcept const&) = default; + ChronometerConcept& operator=(ChronometerConcept const&) = default; + }; + template + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + IDuration elapsed() const { + return std::chrono::duration_cast( + finished - started ); + } + + TimePoint started; + TimePoint finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template + void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable()); } + + int runs() const { return repeats; } + + Chronometer(Detail::ChronometerConcept& meter, int repeats_) + : impl(&meter) + , repeats(repeats_) {} + + private: + template + void measure(Fun&& fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template + void measure(Fun&& fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } + + Detail::ChronometerConcept* impl; + int repeats; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CHRONOMETER_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct is_related + : std::is_same, std::decay_t> {}; + + /// We need to reinvent std::function because every piece of code that might add overhead + /// in a measurement context needs to have consistent performance characteristics so that we + /// can account for it in the measurement. + /// Implementations of std::function with optimizations that aren't always applicable, like + /// small buffer optimizations, are not uncommon. + /// This is effectively an implementation of std::function without any such optimizations; + /// it may be slow, but it is consistently slow. + struct BenchmarkFunction { + private: + struct callable { + virtual void call(Chronometer meter) const = 0; + virtual Catch::Detail::unique_ptr clone() const = 0; + virtual ~callable(); // = default; + + callable() = default; + callable(callable const&) = default; + callable& operator=(callable const&) = default; + }; + template + struct model : public callable { + model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {} + model(Fun const& fun_) : fun(fun_) {} + + Catch::Detail::unique_ptr clone() const override { + return Catch::Detail::make_unique>( *this ); + } + + void call(Chronometer meter) const override { + call(meter, is_callable()); + } + void call(Chronometer meter, std::true_type) const { + fun(meter); + } + void call(Chronometer meter, std::false_type) const { + meter.measure(fun); + } + + Fun fun; + }; + + struct do_nothing { void operator()() const {} }; + + template + BenchmarkFunction(model* c) : f(c) {} + + public: + BenchmarkFunction() + : f(new model{ {} }) {} + + template ::value, int> = 0> + BenchmarkFunction(Fun&& fun) + : f(new model>(CATCH_FORWARD(fun))) {} + + BenchmarkFunction( BenchmarkFunction&& that ) noexcept: + f( CATCH_MOVE( that.f ) ) {} + + BenchmarkFunction(BenchmarkFunction const& that) + : f(that.f->clone()) {} + + BenchmarkFunction& + operator=( BenchmarkFunction&& that ) noexcept { + f = CATCH_MOVE( that.f ); + return *this; + } + + BenchmarkFunction& operator=(BenchmarkFunction const& that) { + f = that.f->clone(); + return *this; + } + + void operator()(Chronometer meter) const { f->call(meter); } + + private: + Catch::Detail::unique_ptr f; + }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_REPEAT_HPP_INCLUDED +#define CATCH_REPEAT_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct repeater { + void operator()(int k) const { + for (int i = 0; i < k; ++i) { + fun(); + } + } + Fun fun; + }; + template + repeater> repeat(Fun&& fun) { + return { CATCH_FORWARD(fun) }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_REPEAT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED +#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_MEASURE_HPP_INCLUDED +#define CATCH_MEASURE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED +#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED + + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct CompleteType { using type = T; }; + template <> + struct CompleteType { struct type {}; }; + + template + using CompleteType_t = typename CompleteType::type; + + template + struct CompleteInvoker { + template + static Result invoke(Fun&& fun, Args&&... args) { + return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + } + }; + template <> + struct CompleteInvoker { + template + static CompleteType_t invoke(Fun&& fun, Args&&... args) { + CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + return {}; + } + }; + + // invoke and not return void :( + template + CompleteType_t> complete_invoke(Fun&& fun, Args&&... args) { + return CompleteInvoker>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); + } + + } // namespace Detail + + template + Detail::CompleteType_t> user_code(Fun&& fun) { + return Detail::complete_invoke(CATCH_FORWARD(fun)); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_TIMING_HPP_INCLUDED +#define CATCH_TIMING_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + template + struct Timing { + IDuration elapsed; + Result result; + int iterations; + }; + template + using TimingOf = Timing>>; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_TIMING_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure(Fun&& fun, Args&&... args) { + auto start = Clock::now(); + auto&& r = Detail::complete_invoke(fun, CATCH_FORWARD(args)...); + auto end = Clock::now(); + auto delta = end - start; + return { delta, CATCH_FORWARD(r), 1 }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_MEASURE_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + TimingOf measure_one(Fun&& fun, int iters, std::false_type) { + return Detail::measure(fun, iters); + } + template + TimingOf measure_one(Fun&& fun, int iters, std::true_type) { + Detail::ChronometerModel meter; + auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); + + return { meter.elapsed(), CATCH_MOVE(result), iters }; + } + + template + using run_for_at_least_argument_t = std::conditional_t::value, Chronometer, int>; + + + [[noreturn]] + void throw_optimized_away_error(); + + template + TimingOf> + run_for_at_least(IDuration how_long, + const int initial_iterations, + Fun&& fun) { + auto iters = initial_iterations; + while (iters < (1 << 30)) { + auto&& Timing = measure_one(fun, iters, is_callable()); + + if (Timing.elapsed >= how_long) { + return { Timing.elapsed, CATCH_MOVE(Timing.result), iters }; + } + iters *= 2; + } + throw_optimized_away_error(); + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + +#include + +namespace Catch { + namespace Benchmark { + struct ExecutionPlan { + int iterations_per_sample; + FDuration estimated_duration; + Detail::BenchmarkFunction benchmark; + FDuration warmup_time; + int warmup_iterations; + + template + std::vector run(const IConfig &cfg, Environment env) const { + // warmup a bit + Detail::run_for_at_least( + std::chrono::duration_cast( warmup_time ), + warmup_iterations, + Detail::repeat( []() { return Clock::now(); } ) + ); + + std::vector times; + const auto num_samples = cfg.benchmarkSamples(); + times.reserve( num_samples ); + for ( size_t i = 0; i < num_samples; ++i ) { + Detail::ChronometerModel model; + this->benchmark( Chronometer( model, iterations_per_sample ) ); + auto sample_time = model.elapsed() - env.clock_cost.mean; + if ( sample_time < FDuration::zero() ) { + sample_time = FDuration::zero(); + } + times.push_back(sample_time / iterations_per_sample); + } + return times; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED +#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_STATS_HPP_INCLUDED +#define CATCH_STATS_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + using sample = std::vector; + + double weighted_average_quantile( int k, + int q, + double* first, + double* last ); + + OutlierClassification + classify_outliers( double const* first, double const* last ); + + double mean( double const* first, double const* last ); + + double normal_cdf( double x ); + + double erfc_inv(double x); + + double normal_quantile(double p); + + Estimate + bootstrap( double confidence_level, + double* first, + double* last, + sample const& resample, + double ( *estimator )( double const*, double const* ) ); + + struct bootstrap_analysis { + Estimate mean; + Estimate standard_deviation; + double outlier_variance; + }; + + bootstrap_analysis analyse_samples(double confidence_level, + unsigned int n_resamples, + double* first, + double* last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_STATS_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + std::vector resolution(int k) { + std::vector> times; + times.reserve(static_cast(k + 1)); + for ( int i = 0; i < k + 1; ++i ) { + times.push_back( Clock::now() ); + } + + std::vector deltas; + deltas.reserve(static_cast(k)); + for ( size_t idx = 1; idx < times.size(); ++idx ) { + deltas.push_back( static_cast( + ( times[idx] - times[idx - 1] ).count() ) ); + } + + return deltas; + } + + constexpr auto warmup_iterations = 10000; + constexpr auto warmup_time = std::chrono::milliseconds(100); + constexpr auto minimum_ticks = 1000; + constexpr auto warmup_seed = 10000; + constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1); + constexpr auto clock_cost_estimation_tick_limit = 100000; + constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10); + constexpr auto clock_cost_estimation_iterations = 10000; + + template + int warmup() { + return run_for_at_least(warmup_time, warmup_seed, &resolution) + .iterations; + } + template + EnvironmentEstimate estimate_clock_resolution(int iterations) { + auto r = run_for_at_least(clock_resolution_estimation_time, iterations, &resolution) + .result; + return { + FDuration(mean(r.data(), r.data() + r.size())), + classify_outliers(r.data(), r.data() + r.size()), + }; + } + template + EnvironmentEstimate estimate_clock_cost(FDuration resolution) { + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FDuration(clock_cost_estimation_time_limit)); + auto time_clock = [](int k) { + return Detail::measure([k] { + for (int i = 0; i < k; ++i) { + volatile auto ignored = Clock::now(); + (void)ignored; + } + }).elapsed; + }; + time_clock(1); + int iters = clock_cost_estimation_iterations; + auto&& r = run_for_at_least(clock_cost_estimation_time, iters, time_clock); + std::vector times; + int nsamples = static_cast(std::ceil(time_limit / r.elapsed)); + times.reserve(static_cast(nsamples)); + for ( int s = 0; s < nsamples; ++s ) { + times.push_back( static_cast( + ( time_clock( r.iterations ) / r.iterations ) + .count() ) ); + } + return { + FDuration(mean(times.data(), times.data() + times.size())), + classify_outliers(times.data(), times.data() + times.size()), + }; + } + + template + Environment measure_environment() { +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + static Catch::Detail::unique_ptr env; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + if (env) { + return *env; + } + + auto iters = Detail::warmup(); + auto resolution = Detail::estimate_clock_resolution(iters); + auto cost = Detail::estimate_clock_cost(resolution.mean); + + env = Catch::Detail::make_unique( Environment{resolution, cost} ); + return *env; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ANALYSE_HPP_INCLUDED +#define CATCH_ANALYSE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED +#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + struct SampleAnalysis { + std::vector samples; + Estimate mean; + Estimate standard_deviation; + OutlierClassification outliers; + double outlier_variance; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + + +namespace Catch { + class IConfig; + + namespace Benchmark { + namespace Detail { + SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ANALYSE_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + namespace Benchmark { + struct Benchmark { + Benchmark(std::string&& benchmarkName) + : name(CATCH_MOVE(benchmarkName)) {} + + template + Benchmark(std::string&& benchmarkName , FUN &&func) + : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} + + template + ExecutionPlan prepare(const IConfig &cfg, Environment env) const { + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max(min_time, std::chrono::duration_cast(cfg.benchmarkWarmupTime())); + auto&& test = Detail::run_for_at_least(std::chrono::duration_cast(run_time), 1, fun); + int new_iters = static_cast(std::ceil(min_time * test.iterations / test.elapsed)); + return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), fun, std::chrono::duration_cast(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; + } + + template + void run() { + static_assert( Clock::is_steady, + "Benchmarking clock should be steady" ); + auto const* cfg = getCurrentContext().getConfig(); + + auto env = Detail::measure_environment(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY{ + auto plan = user_code([&] { + return prepare(*cfg, env); + }); + + BenchmarkInfo info { + CATCH_MOVE(name), + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count() + }; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return plan.template run(*cfg, env); + }); + + auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); + BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; + getResultCapture().benchmarkEnded(stats); + } CATCH_CATCH_ANON (TestFailureException const&) { + getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); + } CATCH_CATCH_ALL{ + getResultCapture().benchmarkFailed(translateActiveException()); + // We let the exception go further up so that the + // test case is marked as failed. + std::rethrow_exception(std::current_exception()); + } + } + + // sets lambda to be used in fun *and* executes benchmark! + template ::value, int> = 0> + Benchmark & operator=(Fun func) { + auto const* cfg = getCurrentContext().getConfig(); + if (!cfg->skipBenchmarks()) { + fun = Detail::BenchmarkFunction(func); + run(); + } + return *this; + } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&] + +#if defined(CATCH_CONFIG_PREFIX_ALL) + +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#else + +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#endif + +#endif // CATCH_BENCHMARK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED +#define CATCH_CONSTRUCTOR_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Benchmark { + namespace Detail { + template + struct ObjectStorage + { + ObjectStorage() = default; + + ObjectStorage(const ObjectStorage& other) + { + new(&data) T(other.stored_object()); + } + + ObjectStorage(ObjectStorage&& other) + { + new(data) T(CATCH_MOVE(other.stored_object())); + } + + ~ObjectStorage() { destruct_on_exit(); } + + template + void construct(Args&&... args) + { + new (data) T(CATCH_FORWARD(args)...); + } + + template + std::enable_if_t destruct() + { + stored_object().~T(); + } + + private: + // If this is a constructor benchmark, destruct the underlying object + template + void destruct_on_exit(std::enable_if_t* = nullptr) { destruct(); } + // Otherwise, don't + template + void destruct_on_exit(std::enable_if_t* = nullptr) { } + +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + T& stored_object() { return *reinterpret_cast( data ); } + + T const& stored_object() const { + return *reinterpret_cast( data ); + } +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic pop +#endif + + alignas( T ) unsigned char data[sizeof( T )]{}; + }; + } // namespace Detail + + template + using storage_for = Detail::ObjectStorage; + + template + using destructable_object = Detail::ObjectStorage; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED + +#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED + + +#ifndef CATCH_APPROX_HPP_INCLUDED +#define CATCH_APPROX_HPP_INCLUDED + + + +#ifndef CATCH_TOSTRING_HPP_INCLUDED +#define CATCH_TOSTRING_HPP_INCLUDED + + +#include +#include +#include +#include + + + + +/** \file + * Wrapper for the WCHAR configuration option + * + * We want to support platforms that do not provide `wchar_t`, so we + * sometimes have to disable providing wchar_t overloads through Catch2, + * e.g. the StringMaker specialization for `std::wstring`. + */ + +#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED +#define CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +// We assume that WCHAR should be enabled by default, and only disabled +// for a shortlist (so far only DJGPP) of compilers. + +#if defined(__DJGPP__) +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +#if !defined( CATCH_INTERNAL_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_WCHAR ) +# define CATCH_CONFIG_WCHAR +#endif + +#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED +#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#include +#include +#include +#include + +namespace Catch { + + class ReusableStringStream : Detail::NonCopyable { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + //! Returns the serialized state + std::string str() const; + //! Sets internal state to `str` + void str(std::string const& str); + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// Old versions of GCC do not understand -Wnonnull-compare +#pragma GCC diagnostic ignored "-Wpragmas" +// Streaming a function pointer triggers Waddress and Wnonnull-compare +// on GCC, because it implicitly converts it to bool and then decides +// that the check it uses (a? true : false) is tautological and cannot +// be null... +#pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + + template + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + auto get() -> std::ostream& { return *m_oss; } + }; +} + +#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#ifndef CATCH_VOID_TYPE_HPP_INCLUDED +#define CATCH_VOID_TYPE_HPP_INCLUDED + + +namespace Catch { + namespace Detail { + + template + struct make_void { using type = void; }; + + template + using void_t = typename make_void::type; + + } // namespace Detail +} // namespace Catch + + +#endif // CATCH_VOID_TYPE_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + + +#include + +namespace Catch { + + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector> m_values; + + ~EnumInfo(); + + StringRef lookup( int value ) const; + }; + } // namespace Detail + + class IMutableEnumValuesRegistry { + public: + virtual ~IMutableEnumValuesRegistry(); // = default; + + virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector const& values ) = 0; + + template + Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list values ) { + static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); + std::vector intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); + } + }; + +} // Catch + +#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy{}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + // Bring in global namespace operator<< for ADL lookup in + // `IsStreamInsertable` below. + using ::operator<<; + + namespace Detail { + + inline std::size_t catch_strnlen(const char *str, std::size_t n) { + auto ret = std::char_traits::find(str, n, '\0'); + if (ret != nullptr) { + return static_cast(ret - str); + } + return n; + } + + constexpr StringRef unprintableString = "{?}"_sr; + + //! Encases `string in quotes, and optionally escapes invisibles + std::string convertIntoString( StringRef string, bool escapeInvisibles ); + + //! Encases `string` in quotes, and escapes invisibles if user requested + //! it via CLI + std::string convertIntoString( StringRef string ); + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template + class IsStreamInsertable { + template + static auto test(int) + -> decltype(std::declval() << std::declval(), std::true_type()); + + template + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test(0))::value; + }; + + template + std::string convertUnknownEnumToString( E e ); + + template + std::enable_if_t< + !std::is_enum::value && !std::is_base_of::value, + std::string> convertUnstreamable( T const& ) { + return std::string(Detail::unprintableString); + } + template + std::enable_if_t< + !std::is_enum::value && std::is_base_of::value, + std::string> convertUnstreamable(T const& ex) { + return ex.what(); + } + + + template + std::enable_if_t< + std::is_enum::value, + std::string> convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr p = &bytes[0]; + return std::string(reinterpret_cast(p), bytes->Length); + } +#endif + + } // namespace Detail + + + template + struct StringMaker { + template + static + std::enable_if_t<::Catch::Detail::IsStreamInsertable::value, std::string> + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template + static + std::enable_if_t::value, std::string> + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template + std::string stringify(const T& e) { + return ::Catch::StringMaker>>::convert(e); + } + + template + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast>(e)); + } + +#if defined(_MANAGED) + template + std::string stringify( T^ e ) { + return ::Catch::StringMaker::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker { + static std::string convert(char const * str); + }; + template<> + struct StringMaker { + static std::string convert(char * str); + }; + +#if defined(CATCH_CONFIG_WCHAR) + template<> + struct StringMaker { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker { + static std::string convert(wchar_t * str); + }; +#endif // CATCH_CONFIG_WCHAR + + template + struct StringMaker { + static std::string convert(char const* str) { + return Detail::convertIntoString( + StringRef( str, Detail::catch_strnlen( str, SZ ) ) ); + } + }; + template + struct StringMaker { + static std::string convert(signed char const* str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + template + struct StringMaker { + static std::string convert(unsigned char const* str) { + auto reinterpreted = reinterpret_cast(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + +#if defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker { + static std::string convert(int value); + }; + template<> + struct StringMaker { + static std::string convert(long value); + }; + template<> + struct StringMaker { + static std::string convert(long long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker { + static std::string convert(bool b) { + using namespace std::string_literals; + return b ? "true"s : "false"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(char c); + }; + template<> + struct StringMaker { + static std::string convert(signed char value); + }; + template<> + struct StringMaker { + static std::string convert(unsigned char value); + }; + + template<> + struct StringMaker { + static std::string convert(std::nullptr_t) { + using namespace std::string_literals; + return "nullptr"s; + } + }; + + template<> + struct StringMaker { + static std::string convert(float value); + CATCH_EXPORT static int precision; + }; + + template<> + struct StringMaker { + static std::string convert(double value); + CATCH_EXPORT static int precision; + }; + + template + struct StringMaker { + template + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template + struct StringMaker { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template + struct StringMaker { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template + std::string rangeToString(InputIterator first, Sentinel last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in their headers + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::pair& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include +namespace Catch { + template + struct StringMaker > { + static std::string convert(const std::optional& optional) { + if (optional.has_value()) { + return ::Catch::Detail::stringify(*optional); + } else { + return "{ }"; + } + } + }; + template <> + struct StringMaker { + static std::string convert(const std::nullopt_t&) { + return "{ }"; + } + }; +} +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get(tuple)); + TupleElementPrinter::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + + template + struct StringMaker> { + static std::string convert(const std::tuple& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include +namespace Catch { + template<> + struct StringMaker { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template + struct StringMaker> { + static std::string convert(const std::variant& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + // Import begin/ end from std here + using std::begin; + using std::end; + + namespace Detail { + template + struct is_range_impl : std::false_type {}; + + template + struct is_range_impl()))>> : std::true_type {}; + } // namespace Detail + + template + struct is_range : Detail::is_range_impl {}; + +#if defined(_MANAGED) // Managed types are never ranges + template + struct is_range { + static const bool value = false; + }; +#endif + + template + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector specially + template + std::string rangeToString( std::vector const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template + struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template + struct StringMaker { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + + +} // namespace Catch + +// Separate std::chrono::duration specialization +#include +#include +#include + + +namespace Catch { + +template +struct ratio_string { + static std::string symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); + } +}; + +template <> +struct ratio_string { + static char symbol() { return 'a'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'f'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'p'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'n'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'u'; } +}; +template <> +struct ratio_string { + static char symbol() { return 'm'; } +}; + + //////////// + // std::chrono::duration specializations + template + struct StringMaker> { + static std::string convert(std::chrono::duration const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string::symbol() << 's'; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template + struct StringMaker>> { + static std::string convert(std::chrono::duration> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point specialization + template + struct StringMaker> { + static std::string convert(std::chrono::time_point const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp, timeStampSize - 1); + } + }; +} + + +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ +namespace Catch { \ + template<> struct StringMaker { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return static_cast(enumInfo.lookup( static_cast( value ) )); \ + } \ + }; \ +} + +#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // CATCH_TOSTRING_HPP_INCLUDED + +#include + +namespace Catch { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Sets and validates the new margin (margin >= 0) + void setMargin(double margin); + // Sets and validates the new epsilon (0 < epsilon < 1) + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template ::value>> + Approx operator()( T const& value ) const { + Approx approx( static_cast(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template ::value>> + explicit Approx( T const& value ): Approx(static_cast(value)) + {} + + + template ::value>> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template ::value>> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template ::value>> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template ::value>> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template ::value>> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) < rhs.m_value || lhs == rhs; + } + + template ::value>> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast(rhs) || lhs == rhs; + } + + template ::value>> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast(lhs) > rhs.m_value || lhs == rhs; + } + + template ::value>> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast(rhs) || lhs == rhs; + } + + template ::value>> + Approx& epsilon( T const& newEpsilon ) { + const auto epsilonAsDouble = static_cast(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template ::value>> + Approx& margin( T const& newMargin ) { + const auto marginAsDouble = static_cast(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template ::value>> + Approx& scale( T const& newScale ) { + m_scale = static_cast(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; + +namespace literals { + Approx operator ""_a(long double val); + Approx operator ""_a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker { + static std::string convert(Catch::Approx const& value); +}; + +} // end namespace Catch + +#endif // CATCH_APPROX_HPP_INCLUDED + + +#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED +#define CATCH_ASSERTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED +#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo { + + SourceLineInfo() = delete; + constexpr SourceLineInfo( char const* _file, std::size_t _line ) noexcept: + file( _file ), + line( _line ) + {} + + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + + friend std::ostream& operator << (std::ostream& os, SourceLineInfo const& info); + }; +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +namespace Catch { + + struct AssertionInfo { + // AssertionInfo() = delete; + + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED +#define CATCH_ASSERTION_RESULT_HPP_INCLUDED + + + +#ifndef CATCH_LAZY_EXPR_HPP_INCLUDED +#define CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + class ITransientExpression; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ): + m_isNegated(isNegated) + {} + LazyExpression(LazyExpression const& other) = default; + LazyExpression& operator = ( LazyExpression const& ) = delete; + + explicit operator bool() const { + return m_transientExpression != nullptr; + } + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + +} // namespace Catch + +#endif // CATCH_LAZY_EXPR_HPP_INCLUDED + +#include + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData&& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + StringRef getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED + + +#ifndef CATCH_CONFIG_HPP_INCLUDED +#define CATCH_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_TEST_SPEC_HPP_INCLUDED +#define CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + + +#ifndef CATCH_WILDCARD_PATTERN_HPP_INCLUDED +#define CATCH_WILDCARD_PATTERN_HPP_INCLUDED + + + +#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED +#define CATCH_CASE_SENSITIVE_HPP_INCLUDED + +namespace Catch { + + enum class CaseSensitive { Yes, No }; + +} // namespace Catch + +#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED + +#include + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity ); + bool matches( std::string const& str ) const; + + private: + std::string normaliseString( std::string const& str ) const; + CaseSensitive m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +#endif // CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class IConfig; + struct TestCaseInfo; + class TestCaseHandle; + + class TestSpec { + + class Pattern { + public: + explicit Pattern( std::string const& name ); + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + std::string const& name() const; + private: + virtual void serializeTo( std::ostream& out ) const = 0; + // Writes string that would be reparsed into the pattern + friend std::ostream& operator<<(std::ostream& out, + Pattern const& pattern) { + pattern.serializeTo( out ); + return out; + } + + std::string const m_name; + }; + + class NamePattern : public Pattern { + public: + explicit NamePattern( std::string const& name, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + void serializeTo( std::ostream& out ) const override; + + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + explicit TagPattern( std::string const& tag, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + void serializeTo( std::ostream& out ) const override; + + std::string m_tag; + }; + + struct Filter { + std::vector> m_required; + std::vector> m_forbidden; + + //! Serializes this filter into a string that would be parsed into + //! an equivalent filter + void serializeTo( std::ostream& out ) const; + friend std::ostream& operator<<(std::ostream& out, Filter const& f) { + f.serializeTo( out ); + return out; + } + + bool matches( TestCaseInfo const& testCase ) const; + }; + + static std::string extractFilterName( Filter const& filter ); + + public: + struct FilterMatch { + std::string name; + std::vector tests; + }; + using Matches = std::vector; + using vectorStrings = std::vector; + + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + Matches matchesByFilter( std::vector const& testCases, IConfig const& config ) const; + const vectorStrings & getInvalidSpecs() const; + + private: + std::vector m_filters; + std::vector m_invalidSpecs; + + friend class TestSpecParser; + //! Serializes this test spec into a string that would be parsed into + //! equivalent test spec + void serializeTo( std::ostream& out ) const; + friend std::ostream& operator<<(std::ostream& out, + TestSpec const& spec) { + spec.serializeTo( out ); + return out; + } + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_SPEC_HPP_INCLUDED + + +#ifndef CATCH_OPTIONAL_HPP_INCLUDED +#define CATCH_OPTIONAL_HPP_INCLUDED + + +#include + +namespace Catch { + + // An optional type + template + class Optional { + public: + Optional(): nullableValue( nullptr ) {} + ~Optional() { reset(); } + + Optional( T const& _value ): + nullableValue( new ( storage ) T( _value ) ) {} + Optional( T&& _value ): + nullableValue( new ( storage ) T( CATCH_MOVE( _value ) ) ) {} + + Optional& operator=( T const& _value ) { + reset(); + nullableValue = new ( storage ) T( _value ); + return *this; + } + Optional& operator=( T&& _value ) { + reset(); + nullableValue = new ( storage ) T( CATCH_MOVE( _value ) ); + return *this; + } + + Optional( Optional const& _other ): + nullableValue( _other ? new ( storage ) T( *_other ) : nullptr ) {} + Optional( Optional&& _other ): + nullableValue( _other ? new ( storage ) T( CATCH_MOVE( *_other ) ) + : nullptr ) {} + + Optional& operator=( Optional const& _other ) { + if ( &_other != this ) { + reset(); + if ( _other ) { nullableValue = new ( storage ) T( *_other ); } + } + return *this; + } + Optional& operator=( Optional&& _other ) { + if ( &_other != this ) { + reset(); + if ( _other ) { + nullableValue = new ( storage ) T( CATCH_MOVE( *_other ) ); + } + } + return *this; + } + + void reset() { + if ( nullableValue ) { nullableValue->~T(); } + nullableValue = nullptr; + } + + T& operator*() { + assert(nullableValue); + return *nullableValue; + } + T const& operator*() const { + assert(nullableValue); + return *nullableValue; + } + T* operator->() { + assert(nullableValue); + return nullableValue; + } + const T* operator->() const { + assert(nullableValue); + return nullableValue; + } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + friend bool operator==(Optional const& a, Optional const& b) { + if (a.none() && b.none()) { + return true; + } else if (a.some() && b.some()) { + return *a == *b; + } else { + return false; + } + } + friend bool operator!=(Optional const& a, Optional const& b) { + return !( a == b ); + } + + private: + T* nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +#endif // CATCH_OPTIONAL_HPP_INCLUDED + + +#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED +#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + +#include + +namespace Catch { + + enum class GenerateFrom { + Time, + RandomDevice, + //! Currently equivalent to RandomDevice, but can change at any point + Default + }; + + std::uint32_t generateRandomSeed(GenerateFrom from); + +} // end namespace Catch + +#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED +#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + + +#include +#include +#include + +namespace Catch { + + enum class ColourMode : std::uint8_t; + + namespace Detail { + //! Splits the reporter spec into reporter name and kv-pair options + std::vector splitReporterSpec( StringRef reporterSpec ); + + Optional stringToColourMode( StringRef colourMode ); + } + + /** + * Structured reporter spec that a reporter can be created from + * + * Parsing has been validated, but semantics have not. This means e.g. + * that the colour mode is known to Catch2, but it might not be + * compiled into the binary, and the output filename might not be + * openable. + */ + class ReporterSpec { + std::string m_name; + Optional m_outputFileName; + Optional m_colourMode; + std::map m_customOptions; + + friend bool operator==( ReporterSpec const& lhs, + ReporterSpec const& rhs ); + friend bool operator!=( ReporterSpec const& lhs, + ReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + + public: + ReporterSpec( + std::string name, + Optional outputFileName, + Optional colourMode, + std::map customOptions ); + + std::string const& name() const { return m_name; } + + Optional const& outputFile() const { + return m_outputFileName; + } + + Optional const& colourMode() const { return m_colourMode; } + + std::map const& customOptions() const { + return m_customOptions; + } + }; + + /** + * Parses provided reporter spec string into + * + * Returns empty optional on errors, e.g. + * * field that is not first and not a key+value pair + * * duplicated keys in kv pair + * * unknown catch reporter option + * * empty key/value in an custom kv pair + * * ... + */ + Optional parseReporterSpec( StringRef reporterSpec ); + +} + +#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + + class IStream; + + /** + * `ReporterSpec` but with the defaults filled in. + * + * Like `ReporterSpec`, the semantics are unchecked. + */ + struct ProcessedReporterSpec { + std::string name; + std::string outputFilename; + ColourMode colourMode; + std::map customOptions; + friend bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ); + friend bool operator!=( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + }; + + struct ConfigData { + + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listListeners = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + bool allowZeroTests = false; + + int abortAfter = -1; + uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + + unsigned int shardCount = 1; + unsigned int shardIndex = 0; + + bool skipBenchmarks = false; + bool benchmarkNoAnalysis = false; + unsigned int benchmarkSamples = 100; + double benchmarkConfidenceInterval = 0.95; + unsigned int benchmarkResamples = 100'000; + std::chrono::milliseconds::rep benchmarkWarmupTime = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; + TestRunOrder runOrder = TestRunOrder::Declared; + ColourMode defaultColourMode = ColourMode::PlatformDefault; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string defaultOutputFilename; + std::string name; + std::string processName; + std::vector reporterSpecifications; + + std::vector testsOrTags; + std::vector sectionsToRun; + }; + + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + ~Config() override; // = default in the cpp file + + bool listTests() const; + bool listTags() const; + bool listReporters() const; + bool listListeners() const; + + std::vector const& getReporterSpecs() const; + std::vector const& + getProcessedReporterSpecs() const; + + std::vector const& getTestsOrTags() const override; + std::vector const& getSectionsToRun() const override; + + TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + StringRef name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutUnmatchedTestSpecs() const override; + bool zeroTestsCountAsSuccess() const override; + ShowDurations showDurations() const override; + double minDuration() const override; + TestRunOrder runOrder() const override; + uint32_t rngSeed() const override; + unsigned int shardCount() const override; + unsigned int shardIndex() const override; + ColourMode defaultColourMode() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + bool skipBenchmarks() const override; + bool benchmarkNoAnalysis() const override; + unsigned int benchmarkSamples() const override; + double benchmarkConfidenceInterval() const override; + unsigned int benchmarkResamples() const override; + std::chrono::milliseconds benchmarkWarmupTime() const override; + + private: + // Reads Bazel env vars and applies them to the config + void readBazelEnvVars(); + + ConfigData m_data; + std::vector m_processedReporterSpecs; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; +} // end namespace Catch + +#endif // CATCH_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED +#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED + +#include + +namespace Catch { + //! Returns Catch2's current RNG seed. + std::uint32_t getSeed(); +} + +#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_HPP_INCLUDED +#define CATCH_MESSAGE_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CATCH_CONFIG_PREFIX_MESSAGES configuration option + * + * CATCH_CONFIG_PREFIX_ALL can be used to avoid clashes with other macros + * by prepending CATCH_. This may not be desirable if the only clashes are with + * logger macros such as INFO and WARN. In this cases + * CATCH_CONFIG_PREFIX_MESSAGES can be used to only prefix a small subset + * of relevant macros. + * + */ + +#ifndef CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED +#define CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_PREFIX_MESSAGES) + #define CATCH_CONFIG_PREFIX_MESSAGES +#endif + +#endif // CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + + +#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED +#define CATCH_STREAM_END_STOP_HPP_INCLUDED + + +namespace Catch { + + // Use this in variadic streaming macros to allow + // << +StreamEndStop + // as well as + // << stuff +StreamEndStop + struct StreamEndStop { + constexpr StringRef operator+() const { return StringRef(); } + + template + constexpr friend T const& operator+( T const& value, StreamEndStop ) { + return value; + } + }; + +} // namespace Catch + +#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED +#define CATCH_MESSAGE_INFO_HPP_INCLUDED + + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == (MessageInfo const& other) const { + return sequence == other.sequence; + } + bool operator < (MessageInfo const& other) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + +} // end namespace Catch + +#endif // CATCH_MESSAGE_INFO_HPP_INCLUDED + +#include +#include + +namespace Catch { + + struct SourceLineInfo; + class IResultCapture; + + struct MessageStream { + + template + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ): + m_info(macroName, lineInfo, type) {} + + template + MessageBuilder&& operator << ( T const& value ) && { + m_stream << value; + return CATCH_MOVE(*this); + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder&& builder ); + ScopedMessage( ScopedMessage& duplicate ) = delete; + ScopedMessage( ScopedMessage&& old ) noexcept; + ~ScopedMessage(); + + MessageInfo m_info; + bool m_moved = false; + }; + + class Capturer { + std::vector m_messages; + IResultCapture& m_resultCapture; + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + + Capturer(Capturer const&) = delete; + Capturer& operator=(Capturer const&) = delete; + + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + Catch::Capturer varName( macroName##_catch_sr, \ + CATCH_INTERNAL_LINEINFO, \ + Catch::ResultWas::Info, \ + #__VA_ARGS__##_catch_sr ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + const Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ + Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + + +#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) + #define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) + #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ ) + +#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) (void)(0) + #define CATCH_UNSCOPED_INFO( msg ) (void)(0) + #define CATCH_WARN( msg ) (void)(0) + #define CATCH_CAPTURE( ... ) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) + #define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) + #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ ) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) (void)(0) + #define UNSCOPED_INFO( msg ) (void)(0) + #define WARN( msg ) (void)(0) + #define CAPTURE( ... ) (void)(0) + +#endif // end of user facing macro declarations + + + + +#endif // CATCH_MESSAGE_HPP_INCLUDED + + +#ifndef CATCH_SECTION_INFO_HPP_INCLUDED +#define CATCH_SECTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_TOTALS_HPP_INCLUDED +#define CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::uint64_t total() const; + bool allPassed() const; + bool allOk() const; + + std::uint64_t passed = 0; + std::uint64_t failed = 0; + std::uint64_t failedButOk = 0; + std::uint64_t skipped = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + Counts assertions; + Counts testCases; + }; +} + +#endif // CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct SectionInfo { + // The last argument is ignored, so that people can write + // SECTION("ShortName", "Proper description that is long") and + // still use the `-c` flag comfortably. + SectionInfo( SourceLineInfo const& _lineInfo, std::string _name, + const char* const = nullptr ): + name(CATCH_MOVE(_name)), + lineInfo(_lineInfo) + {} + + std::string name; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +#endif // CATCH_SECTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_SESSION_HPP_INCLUDED +#define CATCH_SESSION_HPP_INCLUDED + + + +#ifndef CATCH_COMMANDLINE_HPP_INCLUDED +#define CATCH_COMMANDLINE_HPP_INCLUDED + + + +#ifndef CATCH_CLARA_HPP_INCLUDED +#define CATCH_CLARA_HPP_INCLUDED + +#if defined( __clang__ ) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +# pragma clang diagnostic ignored "-Wshadow" +# pragma clang diagnostic ignored "-Wdeprecated" +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +# ifdef __has_include +# if __has_include( ) && __cplusplus >= 201703L +# include +# define CLARA_CONFIG_OPTIONAL_TYPE std::optional +# endif +# endif +#endif + + +#include +#include +#include +#include +#include +#include +#include + +namespace Catch { + namespace Clara { + + class Args; + class Parser; + + // enum of result types from a parse + enum class ParseResultType { + Matched, + NoMatch, + ShortCircuitAll, + ShortCircuitSame + }; + + struct accept_many_t {}; + constexpr accept_many_t accept_many {}; + + namespace Detail { + struct fake_arg { + template + operator T(); + }; + + template + struct is_unary_function : std::false_type {}; + + template + struct is_unary_function< + F, + Catch::Detail::void_t()( fake_arg() ) ) + > + > : std::true_type {}; + + // Traits for extracting arg and return type of lambdas (for single + // argument lambdas) + template + struct UnaryLambdaTraits + : UnaryLambdaTraits {}; + + template + struct UnaryLambdaTraits { + static const bool isValid = false; + }; + + template + struct UnaryLambdaTraits { + static const bool isValid = true; + using ArgType = std::remove_const_t>; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Wraps a token coming from a token stream. These may not directly + // correspond to strings as a single string may encode an option + + // its argument if the : or = form is used + enum class TokenType { Option, Argument }; + struct Token { + TokenType type; + StringRef token; + }; + + // Abstracts iterators into args as a stream of tokens, with option + // arguments uniformly handled + class TokenStream { + using Iterator = std::vector::const_iterator; + Iterator it; + Iterator itEnd; + std::vector m_tokenBuffer; + void loadBuffer(); + + public: + explicit TokenStream( Args const& args ); + TokenStream( Iterator it, Iterator itEnd ); + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + size_t count() const { + return m_tokenBuffer.size() + ( itEnd - it ); + } + + Token operator*() const { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + Token const* operator->() const { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + TokenStream& operator++(); + }; + + //! Denotes type of a parsing result + enum class ResultType { + Ok, ///< No errors + LogicError, ///< Error in user-specified arguments for + ///< construction + RuntimeError ///< Error in parsing inputs + }; + + class ResultBase { + protected: + ResultBase( ResultType type ): m_type( type ) {} + virtual ~ResultBase(); // = default; + + + ResultBase(ResultBase const&) = default; + ResultBase& operator=(ResultBase const&) = default; + ResultBase(ResultBase&&) = default; + ResultBase& operator=(ResultBase&&) = default; + + virtual void enforceOk() const = 0; + + ResultType m_type; + }; + + template + class ResultValueBase : public ResultBase { + public: + T const& value() const& { + enforceOk(); + return m_value; + } + T&& value() && { + enforceOk(); + return CATCH_MOVE( m_value ); + } + + protected: + ResultValueBase( ResultType type ): ResultBase( type ) {} + + ResultValueBase( ResultValueBase const& other ): + ResultBase( other ) { + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + } + ResultValueBase( ResultValueBase&& other ): + ResultBase( other ) { + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( CATCH_MOVE(other.m_value) ); + } + + + ResultValueBase( ResultType, T const& value ): + ResultBase( ResultType::Ok ) { + new ( &m_value ) T( value ); + } + ResultValueBase( ResultType, T&& value ): + ResultBase( ResultType::Ok ) { + new ( &m_value ) T( CATCH_MOVE(value) ); + } + + ResultValueBase& operator=( ResultValueBase const& other ) { + if ( m_type == ResultType::Ok ) + m_value.~T(); + ResultBase::operator=( other ); + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + return *this; + } + ResultValueBase& operator=( ResultValueBase&& other ) { + if ( m_type == ResultType::Ok ) m_value.~T(); + ResultBase::operator=( other ); + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( CATCH_MOVE(other.m_value) ); + return *this; + } + + + ~ResultValueBase() override { + if ( m_type == ResultType::Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template <> class ResultValueBase : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template + class BasicResult : public ResultValueBase { + public: + template + explicit BasicResult( BasicResult const& other ): + ResultValueBase( other.type() ), + m_errorMessage( other.errorMessage() ) { + assert( type() != ResultType::Ok ); + } + + template + static auto ok( U&& value ) -> BasicResult { + return { ResultType::Ok, CATCH_FORWARD(value) }; + } + static auto ok() -> BasicResult { return { ResultType::Ok }; } + static auto logicError( std::string&& message ) + -> BasicResult { + return { ResultType::LogicError, CATCH_MOVE(message) }; + } + static auto runtimeError( std::string&& message ) + -> BasicResult { + return { ResultType::RuntimeError, CATCH_MOVE(message) }; + } + + explicit operator bool() const { + return m_type == ResultType::Ok; + } + auto type() const -> ResultType { return m_type; } + auto errorMessage() const -> std::string const& { + return m_errorMessage; + } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultType::LogicError ); + assert( m_type != ResultType::RuntimeError ); + if ( m_type != ResultType::Ok ) + std::abort(); + } + + std::string + m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultType type, + std::string&& message ): + ResultValueBase( type ), m_errorMessage( CATCH_MOVE(message) ) { + assert( m_type != ResultType::Ok ); + } + + using ResultValueBase::ResultValueBase; + using ResultBase::m_type; + }; + + class ParseState { + public: + ParseState( ParseResultType type, + TokenStream remainingTokens ); + + ParseResultType type() const { return m_type; } + TokenStream const& remainingTokens() const& { + return m_remainingTokens; + } + TokenStream&& remainingTokens() && { + return CATCH_MOVE( m_remainingTokens ); + } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult; + using ParserResult = BasicResult; + using InternalParseResult = BasicResult; + + struct HelpColumns { + std::string left; + StringRef descriptions; + }; + + template + ParserResult convertInto( std::string const& source, T& target ) { + std::stringstream ss( source ); + ss >> target; + if ( ss.fail() ) { + return ParserResult::runtimeError( + "Unable to convert '" + source + + "' to destination type" ); + } else { + return ParserResult::ok( ParseResultType::Matched ); + } + } + ParserResult convertInto( std::string const& source, + std::string& target ); + ParserResult convertInto( std::string const& source, bool& target ); + +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template + auto convertInto( std::string const& source, + CLARA_CONFIG_OPTIONAL_TYPE& target ) + -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if ( result ) + target = CATCH_MOVE( temp ); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct BoundRef : Catch::Detail::NonCopyable { + virtual ~BoundRef() = default; + virtual bool isContainer() const; + virtual bool isFlag() const; + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const& arg ) + -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + bool isFlag() const override; + }; + + template struct BoundValueRef : BoundValueRefBase { + T& m_ref; + + explicit BoundValueRef( T& ref ): m_ref( ref ) {} + + ParserResult setValue( std::string const& arg ) override { + return convertInto( arg, m_ref ); + } + }; + + template + struct BoundValueRef> : BoundValueRefBase { + std::vector& m_ref; + + explicit BoundValueRef( std::vector& ref ): m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const& arg ) + -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if ( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool& m_ref; + + explicit BoundFlagRef( bool& ref ): m_ref( ref ) {} + + ParserResult setFlag( bool flag ) override; + }; + + template struct LambdaInvoker { + static_assert( + std::is_same::value, + "Lambda must return void or clara::ParserResult" ); + + template + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + return lambda( arg ); + } + }; + + template <> struct LambdaInvoker { + template + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template + auto invokeLambda( L const& lambda, std::string const& arg ) + -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result ? result + : LambdaInvoker::ReturnType>::invoke( lambda, temp ); + } + + template struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const& lambda ): m_lambda( lambda ) {} + + auto setValue( std::string const& arg ) + -> ParserResult override { + return invokeLambda::ArgType>( + m_lambda, arg ); + } + }; + + template struct BoundManyLambda : BoundLambda { + explicit BoundManyLambda( L const& lambda ): BoundLambda( lambda ) {} + bool isContainer() const override { return true; } + }; + + template struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits::isValid, + "Supplied lambda must take exactly one argument" ); + static_assert( + std::is_same::ArgType, + bool>::value, + "flags must be boolean" ); + + explicit BoundFlagLambda( L const& lambda ): + m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, + TokenStream tokens ) const + -> InternalParseResult = 0; + virtual size_t cardinality() const; + + InternalParseResult parse( Args const& args ) const; + }; + + template + class ComposableParserImpl : public ParserBase { + public: + template + auto operator|( T const& other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template + class ParserRefImpl : public ComposableParserImpl { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr m_ref; + StringRef m_hint; + StringRef m_description; + + explicit ParserRefImpl( std::shared_ptr const& ref ): + m_ref( ref ) {} + + public: + template + ParserRefImpl( accept_many_t, + LambdaT const& ref, + StringRef hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + template ::value>> + ParserRefImpl( T& ref, StringRef hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + template ::value>> + ParserRefImpl( LambdaT const& ref, StringRef hint ): + m_ref( std::make_shared>( ref ) ), + m_hint( hint ) {} + + DerivedT& operator()( StringRef description ) & { + m_description = description; + return static_cast( *this ); + } + DerivedT&& operator()( StringRef description ) && { + m_description = description; + return static_cast( *this ); + } + + auto optional() -> DerivedT& { + m_optionality = Optionality::Optional; + return static_cast( *this ); + } + + auto required() -> DerivedT& { + m_optionality = Optionality::Required; + return static_cast( *this ); + } + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if ( m_ref->isContainer() ) + return 0; + else + return 1; + } + + StringRef hint() const { return m_hint; } + }; + + } // namespace detail + + + // A parser for arguments + class Arg : public Detail::ParserRefImpl { + public: + using ParserRefImpl::ParserRefImpl; + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + }; + + // A parser for options + class Opt : public Detail::ParserRefImpl { + protected: + std::vector m_optNames; + + public: + template + explicit Opt(LambdaT const& ref) : + ParserRefImpl( + std::make_shared>(ref)) {} + + explicit Opt(bool& ref); + + template ::value>> + Opt( LambdaT const& ref, StringRef hint ): + ParserRefImpl( ref, hint ) {} + + template + Opt( accept_many_t, LambdaT const& ref, StringRef hint ): + ParserRefImpl( accept_many, ref, hint ) {} + + template ::value>> + Opt( T& ref, StringRef hint ): + ParserRefImpl( ref, hint ) {} + + Opt& operator[]( StringRef optName ) & { + m_optNames.push_back(optName); + return *this; + } + Opt&& operator[]( StringRef optName ) && { + m_optNames.push_back( optName ); + return CATCH_MOVE(*this); + } + + Detail::HelpColumns getHelpColumns() const; + + bool isMatch(StringRef optToken) const; + + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + + Detail::Result validate() const override; + }; + + // Specifies the name of the executable + class ExeName : public Detail::ComposableParserImpl { + std::shared_ptr m_name; + std::shared_ptr m_ref; + + public: + ExeName(); + explicit ExeName(std::string& ref); + + template + explicit ExeName(LambdaT const& lambda) : ExeName() { + m_ref = std::make_shared>(lambda); + } + + // The exe name is not parsed out of the normal tokens, but is + // handled specially + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + + std::string const& name() const { return *m_name; } + Detail::ParserResult set(std::string const& newName); + }; + + + // A Combined parser + class Parser : Detail::ParserBase { + mutable ExeName m_exeName; + std::vector m_options; + std::vector m_args; + + public: + + auto operator|=(ExeName const& exeName) -> Parser& { + m_exeName = exeName; + return *this; + } + + auto operator|=(Arg const& arg) -> Parser& { + m_args.push_back(arg); + return *this; + } + + friend Parser& operator|=( Parser& p, Opt const& opt ) { + p.m_options.push_back( opt ); + return p; + } + friend Parser& operator|=( Parser& p, Opt&& opt ) { + p.m_options.push_back( CATCH_MOVE(opt) ); + return p; + } + + Parser& operator|=(Parser const& other); + + template + friend Parser operator|( Parser const& p, T&& rhs ) { + Parser temp( p ); + temp |= rhs; + return temp; + } + + template + friend Parser operator|( Parser&& p, T&& rhs ) { + p |= CATCH_FORWARD(rhs); + return CATCH_MOVE(p); + } + + std::vector getHelpColumns() const; + + void writeToStream(std::ostream& os) const; + + friend auto operator<<(std::ostream& os, Parser const& parser) + -> std::ostream& { + parser.writeToStream(os); + return os; + } + + Detail::Result validate() const override; + + using ParserBase::parse; + Detail::InternalParseResult + parse(std::string const& exeName, + Detail::TokenStream tokens) const override; + }; + + /** + * Wrapper over argc + argv, assumes that the inputs outlive it + */ + class Args { + friend Detail::TokenStream; + StringRef m_exeName; + std::vector m_args; + + public: + Args(int argc, char const* const* argv); + // Helper constructor for testing + Args(std::initializer_list args); + + StringRef exeName() const { return m_exeName; } + }; + + + // Convenience wrapper for option parser that specifies the help option + struct Help : Opt { + Help(bool& showHelpFlag); + }; + + // Result type for parser operation + using Detail::ParserResult; + + namespace Detail { + template + template + Parser + ComposableParserImpl::operator|(T const& other) const { + return Parser() | static_cast(*this) | other; + } + } + + } // namespace Clara +} // namespace Catch + +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_CLARA_HPP_INCLUDED + +namespace Catch { + + struct ConfigData; + + Clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +#endif // CATCH_COMMANDLINE_HPP_INCLUDED + +namespace Catch { + + class Session : Detail::NonCopyable { + public: + + Session(); + ~Session(); + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + Clara::Parser const& cli() const; + void cli( Clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + Clara::Parser m_cli; + ConfigData m_configData; + Detail::unique_ptr m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +#endif // CATCH_SESSION_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED +#define CATCH_TAG_ALIAS_HPP_INCLUDED + + +#include + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo): + tag(_tag), + lineInfo(_lineInfo) + {} + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +#endif // CATCH_TAG_ALIAS_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED +#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +// inside templates (so `TEMPLATE_TEST_CASE` and co). +// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT** +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + + + + +#ifndef CATCH_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEST_MACROS_HPP_INCLUDED + + + +#ifndef CATCH_TEST_MACRO_IMPL_HPP_INCLUDED +#define CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + + +#ifndef CATCH_ASSERTION_HANDLER_HPP_INCLUDED +#define CATCH_ASSERTION_HANDLER_HPP_INCLUDED + + + +#ifndef CATCH_DECOMPOSER_HPP_INCLUDED +#define CATCH_DECOMPOSER_HPP_INCLUDED + + + +#ifndef CATCH_COMPARE_TRAITS_HPP_INCLUDED +#define CATCH_COMPARE_TRAITS_HPP_INCLUDED + + +#include + +namespace Catch { + namespace Detail { + +#if defined( __GNUC__ ) && !defined( __clang__ ) +# pragma GCC diagnostic push + // GCC likes to complain about comparing bool with 0, in the decltype() + // that defines the comparable traits below. +# pragma GCC diagnostic ignored "-Wbool-compare" + // "ordered comparison of pointer with integer zero" same as above, + // but it does not have a separate warning flag to suppress +# pragma GCC diagnostic ignored "-Wextra" + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#if defined( __clang__ ) +# pragma clang diagnostic push + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +# pragma clang diagnostic ignored "-Wfloat-equal" +#endif + +#define CATCH_DEFINE_COMPARABLE_TRAIT( id, op ) \ + template \ + struct is_##id##_comparable : std::false_type {}; \ + template \ + struct is_##id##_comparable< \ + T, \ + U, \ + void_t() op std::declval() )>> \ + : std::true_type {}; \ + template \ + struct is_##id##_0_comparable : std::false_type {}; \ + template \ + struct is_##id##_0_comparable() op 0 )>> \ + : std::true_type {}; + + // We need all 6 pre-spaceship comparison ops: <, <=, >, >=, ==, != + CATCH_DEFINE_COMPARABLE_TRAIT( lt, < ) + CATCH_DEFINE_COMPARABLE_TRAIT( le, <= ) + CATCH_DEFINE_COMPARABLE_TRAIT( gt, > ) + CATCH_DEFINE_COMPARABLE_TRAIT( ge, >= ) + CATCH_DEFINE_COMPARABLE_TRAIT( eq, == ) + CATCH_DEFINE_COMPARABLE_TRAIT( ne, != ) + +#undef CATCH_DEFINE_COMPARABLE_TRAIT + +#if defined( __GNUC__ ) && !defined( __clang__ ) +# pragma GCC diagnostic pop +#endif +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_COMPARE_TRAITS_HPP_INCLUDED + + +#ifndef CATCH_LOGICAL_TRAITS_HPP_INCLUDED +#define CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include + +namespace Catch { +namespace Detail { + +#if defined( __cpp_lib_logical_traits ) && __cpp_lib_logical_traits >= 201510 + + using std::conjunction; + using std::disjunction; + using std::negation; + +#else + + template struct conjunction : std::true_type {}; + template struct conjunction : B1 {}; + template + struct conjunction + : std::conditional_t, B1> {}; + + template struct disjunction : std::false_type {}; + template struct disjunction : B1 {}; + template + struct disjunction + : std::conditional_t> {}; + + template + struct negation : std::integral_constant {}; + +#endif + +} // namespace Detail +} // namespace Catch + +#endif // CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include +#include + +/** \file + * Why does decomposing look the way it does: + * + * Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into + * `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first, + * and our custom operator is used for `a == b`, because `a` is transformed + * into `ExprLhs` and then into `BinaryExpr`. + * + * In practice, decomposing ends up a mess, because we have to support + * various fun things. + * + * 1) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with pointer constructor and deleted + * other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++ + * + * 2) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with consteval integer constructor. + * Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL. + * + * 3) Types that have no linkage, and so we cannot form a reference to + * them. Example: some implementations of traits. + * + * 4) Starting with C++20, when the compiler sees `a == b`, it also uses + * `b == a` when constructing the overload set. For us this means that + * when the compiler handles `ExprLhs == b`, it also tries to resolve + * the overload set for `b == ExprLhs`. + * + * To accomodate these use cases, decomposer ended up rather complex. + * + * 1) These types are handled by adding SFINAE overloads to our comparison + * operators, checking whether `T == U` are comparable with the given + * operator, and if not, whether T (or U) are comparable with literal 0. + * If yes, the overload compares T (or U) with 0 literal inline in the + * definition. + * + * Note that for extra correctness, we check that the other type is + * either an `int` (literal 0 is captured as `int` by templates), or + * a `long` (some platforms use 0L for `NULL` and we want to support + * that for pointer comparisons). + * + * 2) For these types, `is_foo_comparable` is true, but letting + * them fall into the overload that actually does `T == int` causes + * compilation error. Handling them requires that the decomposition + * is `constexpr`, so that P2564R3 applies and the `consteval` from + * their accompanying magic type is propagated through the `constexpr` + * call stack. + * + * However this is not enough to handle these types automatically, + * because our default is to capture types by reference, to avoid + * runtime copies. While these references cannot become dangling, + * they outlive the constexpr context and thus the default capture + * path cannot be actually constexpr. + * + * The solution is to capture these types by value, by explicitly + * specializing `Catch::capture_by_value` for them. Catch2 provides + * specialization for `std::foo_ordering`s, but users can specialize + * the trait for their own types as well. + * + * 3) If a type has no linkage, we also cannot capture it by reference. + * The solution is once again to capture them by value. We handle + * the common cases by using `std::is_arithmetic` as the default + * for `Catch::capture_by_value`, but that is only a some-effort + * heuristic. But as with 2), users can specialize `capture_by_value` + * for their own types as needed. + * + * 4) To support C++20 and make the SFINAE on our decomposing operators + * work, the SFINAE has to happen in return type, rather than in + * a template type. This is due to our use of logical type traits + * (`conjunction`/`disjunction`/`negation`), that we use to workaround + * an issue in older (9-) versions of GCC. I still blame C++20 for + * this, because without the comparison order switching, the logical + * traits could still be used in template type. + * + * There are also other side concerns, e.g. supporting both `REQUIRE(a)` + * and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot + * nicely into the same expression handling logic, but these are rather + * straightforward and add only a bit of complexity (e.g. common base + * class for decomposed expressions). + */ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#pragma warning(disable:4800) // Forcing result to true or false +#endif + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +#if defined(CATCH_CPP20_OR_GREATER) && __has_include() +# include +# if defined( __cpp_lib_three_way_comparison ) && \ + __cpp_lib_three_way_comparison >= 201907L +# define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS +# endif +#endif + +namespace Catch { + + namespace Detail { + // This was added in C++20, but we require only C++14 for now. + template + using RemoveCVRef_t = std::remove_cv_t>; + } + + // Note: There is nothing that stops us from extending this, + // e.g. to `std::is_scalar`, but the more encompassing + // traits are usually also more expensive. For now we + // keep this as it used to be and it can be changed later. + template + struct capture_by_value + : std::integral_constant{}> {}; + +#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) + template <> + struct capture_by_value : std::true_type {}; + template <> + struct capture_by_value : std::true_type {}; + template <> + struct capture_by_value : std::true_type {}; +#endif + + template + struct always_false : std::false_type {}; + + class ITransientExpression { + bool m_isBinaryExpression; + bool m_result; + + public: + constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + constexpr auto getResult() const -> bool { return m_result; } + //! This function **has** to be overriden by the derived class. + virtual void streamReconstructedExpression( std::ostream& os ) const; + + constexpr ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + ITransientExpression() = default; + ITransientExpression(ITransientExpression const&) = default; + ITransientExpression& operator=(ITransientExpression const&) = default; + + friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) { + expr.streamReconstructedExpression(out); + return out; + } + + protected: + ~ITransientExpression() = default; + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + constexpr BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + + template + auto operator && ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator || ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator == ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator != ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator > ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator < ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator >= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + auto operator <= ( T ) const -> BinaryExpr const { + static_assert(always_false::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + }; + + template + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit constexpr UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, static_cast(lhs) }, + m_lhs( lhs ) + {} + }; + + + template + class ExprLhs { + LhsT m_lhs; + public: + explicit constexpr ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( id, op ) \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + Detail::negation>>>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_eq_0_comparable, \ + /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction, \ + std::is_same>>::value, \ + BinaryExpr> { \ + if ( rhs != 0 ) { throw_test_failure_exception(); } \ + return { \ + static_cast( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_eq_0_comparable, \ + /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction, \ + std::is_same>>::value, \ + BinaryExpr> { \ + if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ + return { static_cast( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( eq, == ) + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( ne, != ) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR + + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( id, op ) \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + Detail::negation>>>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction, \ + capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_##id##_0_comparable, \ + std::is_same>::value, \ + BinaryExpr> { \ + if ( rhs != 0 ) { throw_test_failure_exception(); } \ + return { \ + static_cast( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation>, \ + Detail::is_##id##_0_comparable, \ + std::is_same>::value, \ + BinaryExpr> { \ + if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ + return { static_cast( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( lt, < ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( le, <= ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( gt, > ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( ge, >= ) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR + + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( op ) \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + !capture_by_value>::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t::value, \ + BinaryExpr> { \ + return { \ + static_cast( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR + + template + friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr { + static_assert(always_false::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template + friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr { + static_assert(always_false::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + constexpr auto makeUnaryExpr() const -> UnaryExpr { + return UnaryExpr{ m_lhs }; + } + }; + + struct Decomposer { + template >::value, + int> = 0> + constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs { + return ExprLhs{ lhs }; + } + + template ::value, int> = 0> + constexpr friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs { + return ExprLhs{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_DECOMPOSER_HPP_INCLUDED + +#include + +namespace Catch { + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + bool shouldSkip = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + + template + void handleExpr( ExprLhs const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, StringRef message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str ); + +} // namespace Catch + +#endif // CATCH_ASSERTION_HANDLER_HPP_INCLUDED + + +#ifndef CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED +#define CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__##_catch_sr +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION"_catch_sr +#endif + +#endif // CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9 + #pragma GCC diagnostic ignored "-Wparentheses" +#endif + +#if !defined(CATCH_CONFIG_DISABLE) + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { (handler).handleUnexpectedInflightException(); } + +#endif + +#define INTERNAL_CATCH_REACT( handler ) handler.complete(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { /* NOLINT(bugprone-infinite-loop) */ \ + /* The expression should not be evaluated, but warnings should hopefully be checked */ \ + CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); /* NOLINT(bugprone-chained-comparison) */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( (void)0, (false) && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(expr); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + + + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + +#ifndef CATCH_SECTION_HPP_INCLUDED +#define CATCH_SECTION_HPP_INCLUDED + + + + +/** \file + * Wrapper for the STATIC_ANALYSIS_SUPPORT configuration option + * + * Some of Catch2's macros can be defined differently to work better with + * static analysis tools, like clang-tidy or coverity. + * Currently the main use case is to show that `SECTION`s are executed + * exclusively, and not all in one run of a `TEST_CASE`. + */ + +#ifndef CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED +#define CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + + +#if defined(__clang_analyzer__) || defined(__COVERITY__) + #define CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT +#endif + +#if defined( CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT ) && \ + !defined( CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) && \ + !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) +# define CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT +#endif + + +#endif // CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + + +#ifndef CATCH_TIMER_HPP_INCLUDED +#define CATCH_TIMER_HPP_INCLUDED + +#include + +namespace Catch { + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +#endif // CATCH_TIMER_HPP_INCLUDED + +namespace Catch { + + class Section : Detail::NonCopyable { + public: + Section( SectionInfo&& info ); + Section( SourceLineInfo const& _lineInfo, + StringRef _name, + const char* const = nullptr ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) +# define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section ) = \ + Catch::Section( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section ) = \ + Catch::SectionInfo( \ + CATCH_INTERNAL_LINEINFO, \ + ( Catch::ReusableStringStream() << __VA_ARGS__ ) \ + .str() ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#else + +// These section definitions imply that at most one section at one level +// will be intered (because only one section's __LINE__ can be equal to +// the dummy `catchInternalSectionHint` variable from `TEST_CASE`). + +namespace Catch { + namespace Detail { + // Intentionally without linkage, as it should only be used as a dummy + // symbol for static analysis. + // The arguments are used as a dummy for checking warnings in the passed + // expressions. + int GetNewSectionHint( StringRef, const char* const = nullptr ); + } // namespace Detail +} // namespace Catch + + +# define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ + catchInternalSectionHint, \ + catchInternalSectionHint = \ + Catch::Detail::GetNewSectionHint(__VA_ARGS__); \ + catchInternalPreviousSectionHint == __LINE__ ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ + catchInternalSectionHint, \ + catchInternalSectionHint = Catch::Detail::GetNewSectionHint( \ + ( Catch::ReusableStringStream() << __VA_ARGS__ ).str()); \ + catchInternalPreviousSectionHint == __LINE__ ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif + + +#endif // CATCH_SECTION_HPP_INCLUDED + + +#ifndef CATCH_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED +#define CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + +namespace Catch { + + class ITestInvoker { + public: + virtual void invoke() const = 0; + virtual ~ITestInvoker(); // = default + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + + +#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED +#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +#define INTERNAL_CATCH_EXPAND1( param ) INTERNAL_CATCH_EXPAND2( param ) +#define INTERNAL_CATCH_EXPAND2( ... ) INTERNAL_CATCH_NO##__VA_ARGS__ +#define INTERNAL_CATCH_DEF( ... ) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS( ... ) \ + INTERNAL_CATCH_EXPAND1( INTERNAL_CATCH_DEF __VA_ARGS__ ) + +#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +// GCC 5 and older do not properly handle disabling unused-variable warning +// with a _Pragma. This means that we have to leak the suppression to the +// user code as well :-( +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + + + +namespace Catch { + +template +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +Detail::unique_ptr makeTestInvoker( void(*testAsFunction)() ); + +template +Detail::unique_ptr makeTestInvoker( void (C::*testAsMethod)() ) { + return Detail::make_unique>( testAsMethod ); +} + +struct NameAndTags { + constexpr NameAndTags( StringRef name_ = StringRef(), + StringRef tags_ = StringRef() ) noexcept: + name( name_ ), tags( tags_ ) {} + StringRef name; + StringRef tags; +}; + +struct AutoReg : Detail::NonCopyable { + AutoReg( Detail::unique_ptr invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; +}; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static inline void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() +#endif + + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__ ) + +#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + + +// Dummy registrator for the dumy test case macros +namespace Catch { + namespace Detail { + struct DummyUse { + DummyUse( void ( * )( int ), Catch::NameAndTags const& ); + }; + } // namespace Detail +} // namespace Catch + +// Note that both the presence of the argument and its exact name are +// necessary for the section support. + +// We provide a shadowed variable so that a `SECTION` inside non-`TEST_CASE` +// tests can compile. The redefined `TEST_CASE` shadows this with param. +static int catchInternalSectionHint = 0; + +# define INTERNAL_CATCH_TESTCASE2( fname, ... ) \ + static void fname( int ); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + static const Catch::Detail::DummyUse INTERNAL_CATCH_UNIQUE_NAME( \ + dummyUser )( &(fname), Catch::NameAndTags{ __VA_ARGS__ } ); \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + static void fname( [[maybe_unused]] int catchInternalSectionHint ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +# define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( dummyFunction ), __VA_ARGS__ ) + + +#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ + Catch::makeTestInvoker( &TestName::test ), \ + CATCH_INTERNAL_LINEINFO, \ + #ClassName##_catch_sr, \ + Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) + + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ + Catch::makeTestInvoker( &QualifiedMethod ), \ + CATCH_INTERNAL_LINEINFO, \ + "&" #QualifiedMethod##_catch_sr, \ + Catch::NameAndTags{ __VA_ARGS__ } ); \ + } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + do { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } while(false) + + +#endif // CATCH_TEST_REGISTRY_HPP_INCLUDED + + +// All of our user-facing macros support configuration toggle, that +// forces them to be defined prefixed with CATCH_. We also like to +// support another toggle that can minimize (disable) their implementation. +// Given this, we have 4 different configuration options below + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #else + #define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) CATCH_CHECK( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ ) + #endif + + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled + + #define CATCH_REQUIRE( ... ) (void)(0) + #define CATCH_REQUIRE_FALSE( ... ) (void)(0) + + #define CATCH_REQUIRE_THROWS( ... ) (void)(0) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + + #define CATCH_CHECK( ... ) (void)(0) + #define CATCH_CHECK_FALSE( ... ) (void)(0) + #define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) + #define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CATCH_CHECK_NOFAIL( ... ) (void)(0) + + #define CATCH_CHECK_THROWS( ... ) (void)(0) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_CHECK_NOTHROW( ... ) (void)(0) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define CATCH_SECTION( ... ) + #define CATCH_DYNAMIC_SECTION( ... ) + #define CATCH_FAIL( ... ) (void)(0) + #define CATCH_FAIL_CHECK( ... ) (void)(0) + #define CATCH_SUCCEED( ... ) (void)(0) + #define CATCH_SKIP( ... ) (void)(0) + + #define CATCH_STATIC_REQUIRE( ... ) (void)(0) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define CATCH_STATIC_CHECK( ... ) (void)(0) + #define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + #define CATCH_GIVEN( desc ) + #define CATCH_AND_GIVEN( desc ) + #define CATCH_WHEN( desc ) + #define CATCH_AND_WHEN( desc ) + #define CATCH_THEN( desc ) + #define CATCH_AND_THEN( desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented + + #define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #define STATIC_CHECK( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #else + #define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) + #define STATIC_CHECK( ... ) CHECK( __VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ ) + #endif + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled + + #define REQUIRE( ... ) (void)(0) + #define REQUIRE_FALSE( ... ) (void)(0) + + #define REQUIRE_THROWS( ... ) (void)(0) + #define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define REQUIRE_NOTHROW( ... ) (void)(0) + + #define CHECK( ... ) (void)(0) + #define CHECK_FALSE( ... ) (void)(0) + #define CHECKED_IF( ... ) if (__VA_ARGS__) + #define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CHECK_NOFAIL( ... ) (void)(0) + + #define CHECK_THROWS( ... ) (void)(0) + #define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CHECK_NOTHROW( ... ) (void)(0) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define METHOD_AS_TEST_CASE( method, ... ) + #define REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define SECTION( ... ) + #define DYNAMIC_SECTION( ... ) + #define FAIL( ... ) (void)(0) + #define FAIL_CHECK( ... ) (void)(0) + #define SUCCEED( ... ) (void)(0) + #define SKIP( ... ) (void)(0) + + #define STATIC_REQUIRE( ... ) (void)(0) + #define STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define STATIC_CHECK( ... ) (void)(0) + #define STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + + #define GIVEN( desc ) + #define AND_GIVEN( desc ) + #define WHEN( desc ) + #define AND_WHEN( desc ) + #define THEN( desc ) + #define AND_THEN( desc ) + +#endif // ^^ unprefixed, disabled + +// end of user facing macros + +#endif // CATCH_TEST_MACROS_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED +#define CATCH_PREPROCESSOR_HPP_INCLUDED + + +#if defined(__GNUC__) +// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work +#pragma GCC system_header +#endif + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template